Compare commits
1517 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 466e834680 | |||
| 9e0b9962ce | |||
| 4d2863baf5 | |||
| 0032f9a960 | |||
| 5ca31508fb | |||
| a58fde367c | |||
| 3f41910eb4 | |||
| 9a201566f2 | |||
| a180cb95dc | |||
| fbb757fa2f | |||
| 6ee38adbb5 | |||
| 6f3dca44a3 | |||
| a4ae5b74a4 | |||
| fc970a1af4 | |||
| b5748fee6e | |||
| b092dfa2c9 | |||
| 2ad1fbd771 | |||
| 1ca594bbcb | |||
| e66611c965 | |||
| c5a95dc682 | |||
| d7ccbfef20 | |||
| 79f35bb553 | |||
| 9e30e5c34c | |||
| 4963a9668f | |||
| e59bd71c17 | |||
| 5750d05891 | |||
| 1810a911d4 | |||
| 51ae0f0377 | |||
| 215b648edf | |||
| d73f01701e | |||
| 010b282679 | |||
| 6ef8291b2e | |||
| b820810afe | |||
| cef21860eb | |||
| 0c08656965 | |||
| 59b1460e30 | |||
| b75ac1243a | |||
| 93af2b7b1d | |||
| 7092edfe4c | |||
| 0663335011 | |||
| 6cc0c2be91 | |||
| 4a256ed7ca | |||
| 2a526a815e | |||
| 0a81658881 | |||
| 5ac95a0507 | |||
| 77e36ed313 | |||
| 52933e977b | |||
| cf4c29a57f | |||
| 90cd381efa | |||
| 9405f415d0 | |||
| 3dcc130903 | |||
| 9d022180e7 | |||
| ba42d8d8c6 | |||
| 117887ff3a | |||
| b62fa8190b | |||
| eadd4a7bb2 | |||
| a93a1b8b48 | |||
| 12cbe70371 | |||
| ed3367244c | |||
| b8b69eecb8 | |||
| 65acd5d827 | |||
| 4d6732cd19 | |||
| dc5942115d | |||
| c14492a6cf | |||
| 07682c396e | |||
| 503305cbdb | |||
| 32f592444a | |||
| 04813e452c | |||
| b50d9f5c59 | |||
| 40cb70fca1 | |||
| 0b0bbd4c42 | |||
| 4704f2b08e | |||
| f9bd11f6fb | |||
| 0d73e961a4 | |||
| e1f759e70e | |||
| 66380566c8 | |||
| 8cb69e4d5a | |||
| 8ee2c849b4 | |||
| 564d874869 | |||
| d38fcd2db4 | |||
| 06e20b38b5 | |||
| c8b7449b25 | |||
| 6edfebdfed | |||
| c4b39a4fb1 | |||
| cdcca3da1c | |||
| 44d1f169c3 | |||
| d805ee6f3d | |||
| ed2427852b | |||
| 5af894047e | |||
| fced0c4952 | |||
| 609893f7ec | |||
| 3f795a8918 | |||
| a5907db5d8 | |||
| ad7b70a354 | |||
| d992772492 | |||
| 7ba879c441 | |||
| 3fa2238e9c | |||
| 274136dacb | |||
| d5d65cfd56 | |||
| 17c11d09fc | |||
| 6bf42173a1 | |||
| c8649a77d2 | |||
| d780d56b9a | |||
| 2587b549a0 | |||
| dd805c4c25 | |||
| 5cd1892dee | |||
| f575e56e46 | |||
| af7974adda | |||
| 685811ef63 | |||
| 12ae36e99e | |||
| abfe683763 | |||
| a1ab8de593 | |||
| b04c5aec88 | |||
| b862927c8c | |||
| 5744e16a07 | |||
| 11f0a59031 | |||
| 8dfbf342fd | |||
| 5f947a86c7 | |||
| 0f18e78bb4 | |||
| d0c5d614f7 | |||
| e2e1c610d8 | |||
| 1e74aa3087 | |||
| f54a141918 | |||
| b54d8c53a7 | |||
| 016cabb7b2 | |||
| 9129266f54 | |||
| e8376d5b79 | |||
| cc7b1a5642 | |||
| f11fe14cdd | |||
| 219e6b420e | |||
| 3a6ef8c34b | |||
| 4f318a5981 | |||
| f978a5c754 | |||
| f378e10752 | |||
| 30909d13ba | |||
| 8cd03c9624 | |||
| 8f617d01d4 | |||
| 569ac2bc5b | |||
| c1be014499 | |||
| 7d1ac0248b | |||
| aa8e504ef7 | |||
| 6963141eb8 | |||
| 3295b1c8de | |||
| 0159d25e48 | |||
| abbfe04642 | |||
| 8cf3c7b826 | |||
| 0fb927e709 | |||
| c00b248d1a | |||
| d2b4d1e183 | |||
| 8fb9140c21 | |||
| cb9d828ba0 | |||
| 467c0dfffa | |||
| d887a4aa4a | |||
| 34835752f8 | |||
| b9aa78b987 | |||
| d8fa782f13 | |||
| f3d7b249be | |||
| 097836d193 | |||
| 7552ad5edb | |||
| e9cf0a71d8 | |||
| cf731d7296 | |||
| 946e6fd4c2 | |||
| fe9d1a053e | |||
| 6b1ac499ba | |||
| 0fc0b2a1be | |||
| 6887ed4bf7 | |||
| 8cb82df055 | |||
| 6788214671 | |||
| 4c2488ff65 | |||
| 05b101b612 | |||
| 65ab47dc63 | |||
| 23579df8da | |||
| 2b91be6237 | |||
| f1fabb07bf | |||
| 8aea015452 | |||
| 83471228e7 | |||
| e97a647b54 | |||
| f5dceb0b09 | |||
| d4fbdeee94 | |||
| f529cfe8cf | |||
| 4d36aaff39 | |||
| f07aec457a | |||
| d898c80e65 | |||
| 2ff0abc6e7 | |||
| 0276e65f39 | |||
| 4cd8e6b444 | |||
| 58357a0746 | |||
| d0223e6f48 | |||
| 142ea68dce | |||
| 654d2ab63d | |||
| a30f281478 | |||
| ae1baf5789 | |||
| 3c88bdc60c | |||
| 6d698e584b | |||
| 193b989342 | |||
| 8dc322d7f2 | |||
| eee5d710e7 | |||
| 489db37bbd | |||
| 10b5d5e56b | |||
| 934bcf253e | |||
| 0683ba3a54 | |||
| a85f200450 | |||
| 98e0a5f101 | |||
| fb9e11b7b5 | |||
| e77ca024d7 | |||
| 63f8217cd5 | |||
| 9f5cb3a07e | |||
| e09d1b42f2 | |||
| 267d9333a1 | |||
| ab39ac29e8 | |||
| 6c25feabf2 | |||
| 171157c100 | |||
| 3137d7cf2c | |||
| 6ab528748e | |||
| 621dfc136e | |||
| 9d07f5dc2e | |||
| 48dd86bd9b | |||
| 4879582896 | |||
| 6584f86bce | |||
| 82d517fcbb | |||
| ff371c43e0 | |||
| 0bf0e1808c | |||
| 6be5947f69 | |||
| b1d7d7304d | |||
| 1d0702e143 | |||
| 298ce335b9 | |||
| e04f630f41 | |||
| 7dfee16e25 | |||
| d6c21e2af6 | |||
| ebae0a7248 | |||
| fe834f00a2 | |||
| 3855d44bf5 | |||
| ad8f621287 | |||
| 18305a5bf6 | |||
| b0b73bf166 | |||
| 7a8971b9b6 | |||
| c082e5f6a6 | |||
| ef0703f209 | |||
| 196c122e0a | |||
| 57d5a55ca0 | |||
| 94e09f9fa0 | |||
| 2f4bd6a0c9 | |||
| 58d3555102 | |||
| b032c329e3 | |||
| 73f00d2bc3 | |||
| cf896bbb52 | |||
| ba162e35c4 | |||
| 00e9178d5f | |||
| 62b0995f6a | |||
| 4107e5cc9a | |||
| c841146968 | |||
| 6c9e6cc198 | |||
| 980bfddd8a | |||
| 06327e8f04 | |||
| 2290b44f82 | |||
| c0cec2b9de | |||
| e376889f3d | |||
| 8584cacb6e | |||
| 3ff19babf5 | |||
| 8b5aa08c57 | |||
| cffcb2b985 | |||
| 1da8b68906 | |||
| d53f4fb81b | |||
| 02fcd3b489 | |||
| 7b70a7a68a | |||
| 5060725ef5 | |||
| 6bf3218665 | |||
| ab42f84daa | |||
| 29f86afaf4 | |||
| ac5a3b9217 | |||
| d7d73364e9 | |||
| f83a57ae5c | |||
| 56d747818d | |||
| 64b50d13fa | |||
| 0ada6fbd46 | |||
| 2c8840b7e0 | |||
| 1510c5517b | |||
| f6e2fcbf15 | |||
| 55247b4e12 | |||
| f31b26b1ab | |||
| 2b350eb872 | |||
| 88d77658f8 | |||
| cbbc386170 | |||
| 7f9226861a | |||
| 162c79029f | |||
| 5f363f5c9d | |||
| bd5877ad49 | |||
| 71cf7aedb5 | |||
| fa1e0af19f | |||
| a6199680cd | |||
| 9ff4298475 | |||
| 5793216d5c | |||
| d1710826e6 | |||
| 675b996f57 | |||
| 21777a5804 | |||
| b1ca00b037 | |||
| e2a09f272a | |||
| cf9c9fe278 | |||
| 0e77c89d79 | |||
| 7571ae19a0 | |||
| 69c1de7e4a | |||
| 09308e568d | |||
| 671821aeb3 | |||
| 7369331e2d | |||
| e6e193f252 | |||
| 2bb111605c | |||
| 37aba6f7d5 | |||
| 3fc259bb9b | |||
| 15c1a8ee0b | |||
| b4b6de4e0d | |||
| 5b0be7e1a9 | |||
| 92e64927b0 | |||
| 58cde3253d | |||
| 47262b9350 | |||
| 183909f474 | |||
| 06bb43b13e | |||
| 2ed8e1639f | |||
| e78d5b7787 | |||
| e0c6cc1563 | |||
| 472132c236 | |||
| 1262aa19ec | |||
| e9e3f7ef16 | |||
| d9d4c478cc | |||
| db56fc158f | |||
| af8d2490e2 | |||
| fdc77bfd5b | |||
| 5572d37c7f | |||
| 57f2ab2f67 | |||
| 1cab52f555 | |||
| 748233ca70 | |||
| f76108c5b5 | |||
| 09f94f7b56 | |||
| a85b8d6edc | |||
| 52c9761e5b | |||
| c2dbb9cb43 | |||
| 59c3743f77 | |||
| 98422f8476 | |||
| 8c6283a687 | |||
| 1fd492d838 | |||
| fc63a713fd | |||
| e97b9820a4 | |||
| 52f24af701 | |||
| c3032feaf7 | |||
| b30c1523e3 | |||
| 6e1c4c2efd | |||
| 3b542ea008 | |||
| 5841b56f2d | |||
| 85ea29c93c | |||
| f7e979141b | |||
| e0bdc68a9e | |||
| e6cfde211b | |||
| 978aaa5c4b | |||
| b67beaa35a | |||
| 19f23106e7 | |||
| d8a2c59b04 | |||
| dea785f562 | |||
| ca42f86900 | |||
| de9294d1f4 | |||
| 91b83519f5 | |||
| 3eb7f296aa | |||
| f3b0e404f0 | |||
| 3ba3d7ffcf | |||
| 832fa88f08 | |||
| 6e9d349ceb | |||
| 8bd1524abc | |||
| 72f5633061 | |||
| 3b25bdc1cb | |||
| 3690609519 | |||
| 43c9d14ac2 | |||
| 83122fa58b | |||
| f23da7ea75 | |||
| 7ef93e92e6 | |||
| 945f9f8c47 | |||
| 29b40d27e7 | |||
| cd83ed699c | |||
| 02fcded368 | |||
| 18b0dfe28c | |||
| 69be62f1e6 | |||
| 7136c5b414 | |||
| b637e7dde4 | |||
| f545274a0e | |||
| e8c122d5ed | |||
| 9457f63e01 | |||
| df004a2c7a | |||
| a698ee8bc8 | |||
| 1228ffe109 | |||
| 076a10887b | |||
| fc3db2d29c | |||
| 971cdbb918 | |||
| 73adbe9e0c | |||
| 8363609637 | |||
| 705c3a408a | |||
| 228fc5ce73 | |||
| 09885535ac | |||
| de139af5a5 | |||
| d9c7790e82 | |||
| aa03b7d9a0 | |||
| 8f9d0019e6 | |||
| 9e3fae1991 | |||
| 5dcb239253 | |||
| 9fcdea14d7 | |||
| 85dbe83154 | |||
| ee48da2a0a | |||
| f477389159 | |||
| 3bb7f78255 | |||
| 25aecfa9b2 | |||
| f821e504c3 | |||
| d66381863e | |||
| 68712c884e | |||
| db9b31cb85 | |||
| 7090d4e5ba | |||
| b2423fa390 | |||
| 83b87cb812 | |||
| 44dcb4cc78 | |||
| 64803a735d | |||
| 20e77bb5d9 | |||
| f11e5128ad | |||
| bbe13cafcb | |||
| 1acca87fbf | |||
| 2012bb7c53 | |||
| 14d384a30a | |||
| 927f5e2c8b | |||
| d59bedf977 | |||
| c61a71a36e | |||
| 46f2e693bc | |||
| cbc4bb5420 | |||
| 49e85e0680 | |||
| e8d724680a | |||
| 6d9ab5e9ff | |||
| a6d513a0e6 | |||
| d565c40f4b | |||
| 52b4ae5eae | |||
| 0f53dc1b7b | |||
| 5c4b9e59d1 | |||
| a02f63dcc3 | |||
| a3a94e6ea4 | |||
| c039a45def | |||
| 3276dd6b94 | |||
| de306de78b | |||
| 19f96d60e3 | |||
| bfdbf0248c | |||
| 660863392b | |||
| 8baa0f7310 | |||
| 6b18c4f24c | |||
| 40f70a1cc0 | |||
| c869dfea3f | |||
| 39b4263b8b | |||
| cf7016b36d | |||
| 1893ac7ed6 | |||
| a748d105ed | |||
| fc307bcf5b | |||
| 03033a0684 | |||
| 7ca39a5c38 | |||
| d7555e1f29 | |||
| 37726fba58 | |||
| c4cf582c9d | |||
| acc53065e5 | |||
| 9360c109a9 | |||
| 5d9af5a7da | |||
| 88e0fed4c4 | |||
| 98dcc08c6d | |||
| 702b50e7e9 | |||
| 8f946ea1b2 | |||
| 4e5ae0af84 | |||
| ca00871362 | |||
| a1d8658297 | |||
| 31814e8bf9 | |||
| 0028df2564 | |||
| 9b3764212f | |||
| 8128b85706 | |||
| d7c88a38fc | |||
| 0b0b221c02 | |||
| d9b8f46282 | |||
| fbb64db3ae | |||
| 33d8e67a87 | |||
| f05f173d23 | |||
| 81a622a9fa | |||
| 67415dd7e2 | |||
| e8cd6b3be3 | |||
| 7e96e7af83 | |||
| 119b73ead2 | |||
| 365c454da1 | |||
| b6d1f9f754 | |||
| 6719d9670d | |||
| 62234878a1 | |||
| 851a9a7620 | |||
| 61d9002cb1 | |||
| 84fc5dfcf7 | |||
| e1b3ae208d | |||
| 60ec9739c2 | |||
| c9fb3dfb1d | |||
| 8230d1b515 | |||
| 838b4f4e52 | |||
| eae5b154c9 | |||
| ebc870d2d4 | |||
| 986b1aadbf | |||
| 9ced73bf19 | |||
| f0428d5c99 | |||
| 486a0d2c6e | |||
| 13cf6a1007 | |||
| 4c59bcc1bb | |||
| 057bb31d94 | |||
| 6258c0671b | |||
| b3b45458f6 | |||
| 54047a98b3 | |||
| 6411dcbf72 | |||
| 1818215fee | |||
| 1ffceef9f0 | |||
| be1426a5f6 | |||
| 3ebb819ba3 | |||
| 5f16e534ee | |||
| 8f8e6f16b4 | |||
| b5592b88fa | |||
| ba06269ed7 | |||
| e450baeac4 | |||
| 3a81b6a08f | |||
| b7a2efcd6a | |||
| a605d50d69 | |||
| 27d894b8e9 | |||
| 14f1ef8e30 | |||
| 7b471d59c5 | |||
| 5ca102ffee | |||
| 0e04f79b6d | |||
| c141b873c3 | |||
| 3cc97690a4 | |||
| 5b7f08137f | |||
| 00318be59f | |||
| 37c00d4eeb | |||
| 3300d25e57 | |||
| f443b1426f | |||
| 96354ee1d8 | |||
| 41b725714f | |||
| 12560e983c | |||
| 44ff6d50ec | |||
| 29c1cca990 | |||
| 979f7ba78c | |||
| b6dfe01628 | |||
| d4cd5ba231 | |||
| e7cf1e34cf | |||
| c9718de24c | |||
| c58b1f0fd2 | |||
| b720ae5d75 | |||
| 3cea1a99b7 | |||
| 03eb079a5e | |||
| 899db2de78 | |||
| 22338d7058 | |||
| db6d7714a4 | |||
| 2de4f425ff | |||
| a71ab6e2ae | |||
| 0b6bf701ef | |||
| 38eb4eed97 | |||
| bdb54f56d9 | |||
| 3eb2981fb4 | |||
| 835269825d | |||
| 12ab1cb2c4 | |||
| 07f0e3803a | |||
| 3d307fe8ff | |||
| 5cb4a65cbd | |||
| 84ce065f76 | |||
| 6398d7ff23 | |||
| 7e3f8ad012 | |||
| 8703c87880 | |||
| 7e010c88b9 | |||
| dad921bf5b | |||
| e020bafc38 | |||
| ecf5e1ce6b | |||
| 31bcee1a13 | |||
| 6245127ce1 | |||
| d1a4b9ad85 | |||
| 167e615f67 | |||
| 9c1b0471ca | |||
| 96a8a94309 | |||
| d2960aae3c | |||
| 3f1a23615a | |||
| 78de0f3167 | |||
| 718e900a39 | |||
| eff2a857c0 | |||
| cf2b5cc24d | |||
| 7859fadf6b | |||
| 34588b7dc0 | |||
| f6999898d5 | |||
| 821243ce7c | |||
| 44d0abb111 | |||
| 10fd2b291e | |||
| 5034c7c312 | |||
| ff36ba7742 | |||
| 9b2fabfb3e | |||
| 4008eb20a3 | |||
| e224ee8277 | |||
| d236f97605 | |||
| 0efb966b68 | |||
| 63dfa52ea8 | |||
| 74507e9981 | |||
| d24bef7883 | |||
| 59bf0c080a | |||
| e8553cfac0 | |||
| baadf62fc3 | |||
| cf0f232635 | |||
| e2ee7f7b2f | |||
| 76187d31cf | |||
| c3bbedbc56 | |||
| c4312fbb31 | |||
| 1bf71b47df | |||
| 67f26fa1ac | |||
| dafabd5b99 | |||
| 878adfb0c0 | |||
| 3da9f6d0e3 | |||
| f5fdd8a6ab | |||
| 47e4599fe2 | |||
| 81b432dc0e | |||
| 87c5668b14 | |||
| 84da133e95 | |||
| 80e254d46b | |||
| 7bce16f811 | |||
| 0d430be1fa | |||
| 3c76af9dab | |||
| ad12a9eb99 | |||
| 45bf046645 | |||
| a8516ff0f5 | |||
| 2ceda2987c | |||
| e99adc339e | |||
| 1aa863a92f | |||
| a417698fcd | |||
| e49b5b03db | |||
| b5f503dbbb | |||
| 04d3633545 | |||
| 4e585c5c34 | |||
| 867aae868d | |||
| 1f237db2b5 | |||
| 04e8c3ceea | |||
| 592848ad03 | |||
| 22318eb80e | |||
| c03f8afca1 | |||
| 88f8e0b8b1 | |||
| 8f6b831512 | |||
| 85a2c12768 | |||
| 1b8fd63853 | |||
| ae325ff124 | |||
| 1698d96883 | |||
| e35f5c9fde | |||
| 26a59e2cc5 | |||
| 56df7ad9fc | |||
| 52ae7c3615 | |||
| c9e56ab04b | |||
| cfa527e9af | |||
| 6cb17caf91 | |||
| 67a0040732 | |||
| 3913b450b6 | |||
| 13d61e15f4 | |||
| 7d46236900 | |||
| a7e54967c3 | |||
| a440e4ef79 | |||
| b5a52129fb | |||
| 8e72e0ec07 | |||
| 8204ecc131 | |||
| beaf5215b7 | |||
| b3fa997bea | |||
| eab5bfb475 | |||
| 9f5d4dd67f | |||
| fc757f6cf7 | |||
| fd619de1d3 | |||
| 4c0341b232 | |||
| a3bcc9d91e | |||
| d215e3ca68 | |||
| 493fa8541b | |||
| 930b5e07f8 | |||
| 5174717ca9 | |||
| a7681fc712 | |||
| 7d1976ad05 | |||
| d317f33f4e | |||
| 9e53b0742c | |||
| 4c78b8c6d1 | |||
| b88db55db3 | |||
| b8f8d1d4b1 | |||
| c4cbf6de3b | |||
| e7b0e3293d | |||
| 87c8e85068 | |||
| c9ae5e1e9f | |||
| 3d9514d266 | |||
| 3d9cbf52b9 | |||
| 5b4043db85 | |||
| 661d36614f | |||
| 11cef55c03 | |||
| 667d08d003 | |||
| ba780276a2 | |||
| 46db0bc08c | |||
| 0de3c6945b | |||
| 0bf30aaa6b | |||
| faac205464 | |||
| 1d5d7048d6 | |||
| 033643c4ca | |||
| 8b8b5cfac4 | |||
| 9ad32fd846 | |||
| d9766bfd7e | |||
| 186f2a8392 | |||
| c6a43b0d0c | |||
| a1513b7c7c | |||
| 6571ef4e45 | |||
| a3f8a4f45e | |||
| 336111fbd4 | |||
| f100ad12e0 | |||
| 7adc505a00 | |||
| d781ee0596 | |||
| 0ea9240226 | |||
| dbbdb7cd7e | |||
| 2f98d2a781 | |||
| d9ea89857c | |||
| a29a697012 | |||
| 430706529a | |||
| 9b1999bc02 | |||
| 196e82d3ab | |||
| 1761d0e65c | |||
| 9465723026 | |||
| c9246bf946 | |||
| 4f0380bb2c | |||
| 41a4c7c39f | |||
| e3bdf89cab | |||
| 0843f09c86 | |||
| 93afe00205 | |||
| 0f978bbcc7 | |||
| 88c49a9f7c | |||
| 83f285c913 | |||
| 24499c5ea9 | |||
| d02b1f04e2 | |||
| c818003399 | |||
| 306db6d026 | |||
| b75ff4bdf5 | |||
| 60439c7d8e | |||
| 3518a4167c | |||
| 51dd7fa58e | |||
| 0a5549ebc9 | |||
| 607167ebe7 | |||
| 1183517c3a | |||
| 6d169784ed | |||
| 9b16cc6266 | |||
| d56f46e1b1 | |||
| 6a5feacd38 | |||
| 0a91189e6a | |||
| e9d9ebf156 | |||
| 1a85dc9b99 | |||
| 660af7ec70 | |||
| 102d44a7b8 | |||
| 549b0f8ef4 | |||
| c6fe8ae70c | |||
| 9e1af0db63 | |||
| 43b6cb9bff | |||
| 9b7acf33c6 | |||
| 3914abda20 | |||
| e47042d1cb | |||
| a40a2f96b5 | |||
| efc689a937 | |||
| 78bf40eaa9 | |||
| 578604619a | |||
| 6eac798d91 | |||
| 121bdbad84 | |||
| aaeaa02406 | |||
| 5b2b2d1a5c | |||
| 9209d11084 | |||
| 60f54f1bc9 | |||
| 8b1de5d9b5 | |||
| b2095481b3 | |||
| a8e8517364 | |||
| c9b31c62cd | |||
| c72cf64c8a | |||
| 8c2f841972 | |||
| 0c1c750005 | |||
| 33d98dfe8c | |||
| 256815e148 | |||
| cdbfbb5504 | |||
| 753a88c409 | |||
| eef6966a85 | |||
| d1d6a30884 | |||
| 3a190a1c37 | |||
| ae70902921 | |||
| 5418237919 | |||
| 42fcdb33a1 | |||
| 2098e69fb4 | |||
| 2415275e90 | |||
| 83fcc92f75 | |||
| 64a7ab6e2e | |||
| a772e1f9ca | |||
| 7bbed1705c | |||
| 4cd4660357 | |||
| d503724bda | |||
| 2756a1f1a0 | |||
| 883122a98b | |||
| bd7f0383d1 | |||
| aa8cefc110 | |||
| 48740f5032 | |||
| 35b81a2f38 | |||
| 4b582b9d7e | |||
| d72f45a94b | |||
| a71ed3ec5a | |||
| 92b2a6fa24 | |||
| ddeb64964d | |||
| b2d6c5b6a7 | |||
| 271375d171 | |||
| 5693039f0d | |||
| 52304b37a2 | |||
| fe8eabda37 | |||
| a00032de53 | |||
| b320358dc4 | |||
| f81ab8e4d4 | |||
| 9a537248a4 | |||
| eeed65b354 | |||
| 3c6711c590 | |||
| 90d8dfcf12 | |||
| 03b76f3c8a | |||
| b9caed4392 | |||
| 6993a650b0 | |||
| 4000794af6 | |||
| 7c9e6779c6 | |||
| ec6d9ec7de | |||
| 58fccadf08 | |||
| 34e7362132 | |||
| fca39a7809 | |||
| 278404f47c | |||
| 64612712a5 | |||
| 9ffd0ad982 | |||
| e703fec3af | |||
| 78e9eac64a | |||
| 2b1fc8725e | |||
| b885c3dda3 | |||
| aa2c1501da | |||
| ed5c929b23 | |||
| 672a23dbcd | |||
| 24c09d0319 | |||
| 092275a981 | |||
| 3fe97e1709 | |||
| 1ad4f0c9b5 | |||
| 27a327402b | |||
| e2adfe9eab | |||
| 67690c9e8e | |||
| 4c3c2eaa3b | |||
| cf6c41f9db | |||
| 2e0a597ee4 | |||
| 628983d123 | |||
| 8ec5dd6def | |||
| acc40c73f3 | |||
| 1d4658e978 | |||
| b8bbf3b69e | |||
| d8e41fa8b1 | |||
| e9a6ba03f9 | |||
| 2e69e38adb | |||
| c5ed876c09 | |||
| 96f737f13e | |||
| f369151e8e | |||
| d1b9990e4e | |||
| ed7eb01d1b | |||
| 6601127693 | |||
| 43236c1cc4 | |||
| 65bbf7b2a2 | |||
| 74b8dca63e | |||
| 7db0e3ac3f | |||
| f889c24497 | |||
| 852a1900b4 | |||
| 30a273c946 | |||
| 903614140a | |||
| 0fe772efa8 | |||
| e6dedee56f | |||
| 66605e91aa | |||
| 137db2c86c | |||
| 9c28a6ff4a | |||
| 48a8d7de1c | |||
| 5fa96c944d | |||
| fe270bd9ae | |||
| a0912ad6b4 | |||
| 9ba0c54295 | |||
| 86721b88ce | |||
| 9bdee1b82d | |||
| abe01f8f48 | |||
| c5525d356e | |||
| 36ae564d26 | |||
| cba6bd6ef5 | |||
| 4d40ffedff | |||
| 319e8d746f | |||
| fe7a88362b | |||
| 8bf314202f | |||
| ac8bc7dec2 | |||
| 0d5919392e | |||
| 1473abf19a | |||
| 45e75f3d09 | |||
| 5358f2fb9e | |||
| c316d27444 | |||
| 0c0bb29f83 | |||
| 82bc3270e7 | |||
| ec79b0b17b | |||
| 0560567058 | |||
| 3de740b35f | |||
| b69f598e51 | |||
| 1ad177fca7 | |||
| c3ad3ff348 | |||
| ad578bb171 | |||
| 9538c49ac8 | |||
| 84d43db500 | |||
| bf2d0468f9 | |||
| d2bc47cfe8 | |||
| d8c6c766b9 | |||
| 98f4bf2f65 | |||
| f9c8b7f4ab | |||
| a92532c0c9 | |||
| bede81632d | |||
| e975062e0e | |||
| 0cd051cee2 | |||
| 682980be49 | |||
| 878043ea3d | |||
| 757ae65189 | |||
| cd63abba5b | |||
| a0f00d0c83 | |||
| 915591c6d6 | |||
| 157cb7d982 | |||
| cdcb21b841 | |||
| 2bf79a5298 | |||
| ebefefa0eb | |||
| a740883c8d | |||
| da760075d2 | |||
| b8833772a3 | |||
| 131c6e761e | |||
| 1fb0f2f048 | |||
| 2a25ac9d1f | |||
| 773bf765ad | |||
| b0b866eec5 | |||
| 8cc928c841 | |||
| 4180420765 | |||
| 30945dc240 | |||
| 4ef87e3419 | |||
| 4785194527 | |||
| 8a742ce3ec | |||
| 27af9455f5 | |||
| 98ff572afe | |||
| f77fedbea4 | |||
| 39c582dac2 | |||
| 8736728478 | |||
| 23cf9f47b0 | |||
| b8feefc541 | |||
| e3a6c71087 | |||
| d6cc88fa99 | |||
| 428d451e55 | |||
| aa67abad94 | |||
| 415b8354f4 | |||
| 51c596a21f | |||
| 5816a594fd | |||
| f4d76aa360 | |||
| 56835f5f09 | |||
| 255ad7cac5 | |||
| 8c097a81ea | |||
| 3dc92b42fe | |||
| 1ed5a37280 | |||
| cb36f2844e | |||
| 7b93b55b78 | |||
| 6837196e44 | |||
| 31680f3b59 | |||
| 91d9c9b4d5 | |||
| d18d8cf0ea | |||
| 123909cdac | |||
| f0eaccc6ac | |||
| 1d66ef2f2d | |||
| 8aefae3aff | |||
| 514558796b | |||
| b00ca54707 | |||
| 0dbe507b26 | |||
| 4b70bea21f | |||
| 096a051b99 | |||
| a3ca1ff1e9 | |||
| 28eabfe477 | |||
| 0a68b0515c | |||
| 566c0c8f72 | |||
| 333f2cb472 | |||
| 3d1acc65af | |||
| 5907de90c1 | |||
| 25d8a9d1f8 | |||
| b44e8199b5 | |||
| 91449d0c8b | |||
| b228f177f3 | |||
| 9a40a2d4f2 | |||
| cdc3dabba1 | |||
| 40ba2cbe79 | |||
| 7ede8af193 | |||
| a835b8cc8f | |||
| e54b47b857 | |||
| c93c25829b | |||
| 1cdf8b29a5 | |||
| b6eacf0771 | |||
| 4ea23c1bd8 | |||
| 70398820c0 | |||
| 04abc4d12f | |||
| 8491f46045 | |||
| 4aa909e587 | |||
| 9f5e4b5812 | |||
| 06eb5abf11 | |||
| 29e6e3b374 | |||
| 2fc97f83f4 | |||
| 1e761b4c7d | |||
| a2ca136f1f | |||
| 94322664f2 | |||
| cbd5c6d3e9 | |||
| 0428dc8381 | |||
| 01e7b0da46 | |||
| b37dadcc05 | |||
| 8e6151fe65 | |||
| 9b346c0937 | |||
| b09f80ca30 | |||
| 0e0af82290 | |||
| c69b09cbff | |||
| b8ba6cd970 | |||
| 004e30d6be | |||
| b084efdd06 | |||
| d377d8462f | |||
| 2bbec9f9b1 | |||
| b326908487 | |||
| b21156a886 | |||
| 85138b0bc8 | |||
| e0df0257d1 | |||
| d23c1e8f92 | |||
| 670a40e67b | |||
| 3bbc6cbab1 | |||
| 15de53a22d | |||
| 59a44d6973 | |||
| 7561692352 | |||
| f50a633f87 | |||
| 50f07e7b11 | |||
| 660623181a | |||
| 03ee3aaf40 | |||
| ad295946c0 | |||
| c28446dad0 | |||
| ff9d1f2728 | |||
| 0b7cb2a1a8 | |||
| 92af01b0cb | |||
| dc26d0d7ba | |||
| 2fe67edab3 | |||
| 167e43cbef | |||
| d8cb407c25 | |||
| 9452afe3f7 | |||
| 8dc6d9ce7d | |||
| bf4b8a318d | |||
| 20f6022050 | |||
| 65e6c737cd | |||
| 6d2572e347 | |||
| 52a35197c7 | |||
| 2b3a41db3e | |||
| 09c1459411 | |||
| 98f18b89da | |||
| 4249db4330 | |||
| a7f46500ed | |||
| c73fa2b0eb | |||
| db4c4b971a | |||
| e9ccc98ada | |||
| 11d6ec33b3 | |||
| c3b6f4ca85 | |||
| d37405a307 | |||
| d7a9841328 | |||
| cf3af42477 | |||
| ad8e6e7d67 | |||
| ae41ab101a | |||
| f4881e7c6f | |||
| 94ef4380d4 | |||
| 7ebe1b8c14 | |||
| 79020bba28 | |||
| 95c250ebcc | |||
| bfb14614cc | |||
| 12299d4810 | |||
| c67a9d4e84 | |||
| af422c7cd4 | |||
| 51bdecea53 | |||
| 0f505c6d7b | |||
| ae72f10448 | |||
| 9f34153c84 | |||
| c04cf6c5cb | |||
| 5112736385 | |||
| 11eaa6d77c | |||
| 6024903bde | |||
| e96928588e | |||
| 94e38d4cdd | |||
| d15921b3f2 | |||
| 3c6e2b89a4 | |||
| 439658fcf1 | |||
| c9acb1466c | |||
| addd27faf8 | |||
| 5605bdc7a3 | |||
| f1df7e9bdc | |||
| 4765656f87 | |||
| c3a00b93c2 | |||
| e5d2ba7835 | |||
| 8c1b119e84 | |||
| 593a54e6cb | |||
| 95fa7083a9 | |||
| 98e08df807 | |||
| 01da36f984 | |||
| ae155c78c2 | |||
| 9742216479 | |||
| f720183281 | |||
| 4bc84acac6 | |||
| 14a8086677 | |||
| 686b96222e | |||
| 0962f37f55 | |||
| 2c17551b27 | |||
| 3f5a5e35c2 | |||
| 07c6d37ff0 | |||
| 916ea7acc0 | |||
| eae7e865a5 | |||
| 776e5054aa | |||
| bf2b8b280e | |||
| 7aa7027a34 | |||
| d4713b2091 | |||
| 9b97be4aa4 | |||
| 621be11395 | |||
| 05af6df536 | |||
| b7bda2355d | |||
| b0976031ae | |||
| d099f30a34 | |||
| 699be03bb7 | |||
| ccf93cfdcd | |||
| 47358a8229 | |||
| 9856c2cfc4 | |||
| df9669ba2c | |||
| f970454cb4 | |||
| 69678574d4 | |||
| 53cab5074b | |||
| c6c65b1799 | |||
| bb9f64027a | |||
| 5f52fc4338 | |||
| 82e30c8519 | |||
| 6e2aa6b4b8 | |||
| a1681cdd63 | |||
| 611a3ce138 | |||
| 396d933e34 | |||
| 0858e0a21e | |||
| 704e32b0dc | |||
| f95cc960e1 | |||
| 1b00f512c1 | |||
| d9ec66695c | |||
| 1a2578a4b7 | |||
| f714e306da | |||
| 6a4b641250 | |||
| 8d96844312 | |||
| b7145debfb | |||
| 990dbb6c06 | |||
| c6a66a8fac | |||
| 65ba67dedc | |||
| 824db78234 | |||
| 2fdf108198 | |||
| 63092d7d1a | |||
| 964d437c7a | |||
| d49c556291 | |||
| d6b2748293 | |||
| e3a5831d64 | |||
| 08d5174d02 | |||
| dacb926db5 | |||
| c0e9173c9b | |||
| f46308bbdb | |||
| 331db99a4e | |||
| d84f32f27d | |||
| ac28a44b92 | |||
| 003a41b920 | |||
| bc4c693525 | |||
| d4ba837641 | |||
| 4a1592dd92 | |||
| ac145a0b65 | |||
| 50c00dcae6 | |||
| 95d964140b | |||
| 0a73a7eb52 | |||
| 0aecb24930 | |||
| 904f5303a6 | |||
| 585761e931 | |||
| 4d53797cba | |||
| 803677e884 | |||
| 17617c58d7 | |||
| 96d03ec13d | |||
| 4726afbebf | |||
| 97c937e554 | |||
| b25520cf18 | |||
| c6b46d75cb | |||
| a554352ae0 | |||
| a7fd03f1b7 | |||
| ef96116c3f | |||
| 01d10aebca | |||
| 7edc50865f | |||
| a881d3fd81 | |||
| 5e9d2323e3 | |||
| a98b5dd86e | |||
| a250cf7ebe | |||
| 1c5ca0f022 | |||
| ca3dee7b37 | |||
| 59d47955a0 | |||
| d05ca3db0b | |||
| 87678eff98 | |||
| c1f54b14d0 | |||
| 4c487d666f | |||
| 655a300acb | |||
| 8d61d66d79 | |||
| 13f391fe4a | |||
| 660a5d910a | |||
| a58c5b372c | |||
| ec55fdb8d8 | |||
| 19d670826d | |||
| 7033bd19fe | |||
| aea75b9e52 | |||
| eaf68fe105 | |||
| a27237286c | |||
| 502ca6b612 | |||
| a9451a5413 | |||
| ee54047b94 | |||
| a2e2c0a41a | |||
| 8e7bfba0ab | |||
| 6d280be081 | |||
| 877c6635e4 | |||
| ca0be181bc | |||
| ba2cf8015b | |||
| f1f4ed97ca | |||
| 24785d28e6 | |||
| 0076205da6 | |||
| 6235b50c9d | |||
| 5cca19bb83 | |||
| 1b7056853b | |||
| d1bc519599 | |||
| bcae1dce7b | |||
| e87927564b | |||
| 01b7ad4a3f | |||
| 235d38752c | |||
| fd30edf2bb | |||
| 0a2be142ff | |||
| 33614d8ff0 | |||
| 3dce1d32f9 | |||
| 4a62199682 | |||
| 68be56240d | |||
| 19ccb27dbd | |||
| a7e2bf071b | |||
| e05a6e976e | |||
| c7485e3fd4 | |||
| 80ad1a68e7 | |||
| f4440d43de | |||
| 201a5c06fe | |||
| 6bd21b8995 | |||
| ce66fbb595 | |||
| 226ebdd935 | |||
| fcc400b20d | |||
| d2c908b1ab | |||
| 976f9b2590 | |||
| 4d175a466e | |||
| 986ff7d1b1 | |||
| bc719feb5d | |||
| fe41612738 | |||
| 91f36ae42a | |||
| 2750981b64 | |||
| 268683f8ea | |||
| 5737165f37 | |||
| a70116ed4d | |||
| 31d2296777 | |||
| b859d7f3a5 | |||
| 75714ae84a | |||
| be221c5a3e | |||
| 064ff34866 | |||
| 9807d6bb2c | |||
| 8321425e62 | |||
| ba3223a9a3 | |||
| a266997b69 | |||
| 314983b139 | |||
| 8896b0ae7c | |||
| b6aa922c09 | |||
| 95732f4170 | |||
| b6702d1d32 | |||
| c3679910a4 | |||
| 624dfdf6ac | |||
| 83e0cf75c5 | |||
| 19a2e06c0b | |||
| 7fe23ad8c9 | |||
| fd9ec438dc | |||
| 7cab973cbf | |||
| 9e3e5038f7 | |||
| 7952712c5e | |||
| d754a2e295 | |||
| 768b4e5e2b | |||
| b1ae09c003 | |||
| 9408e858cd | |||
| 0995164110 | |||
| b3a042a63b | |||
| c2ac70ff10 | |||
| e7b57d8dee | |||
| 5bc8b02b0f | |||
| d6f96b2c53 | |||
| 6c131b5648 | |||
| 27a4a502d7 | |||
| f0a25ea485 | |||
| 4ca6e61726 | |||
| e26ac6ed00 | |||
| f341edb975 | |||
| fdad24aaac | |||
| 3a676f98db | |||
| 8498cb17e7 | |||
| 36098f6b78 | |||
| 34667c69d3 | |||
| 756fef53f9 | |||
| 2ffe938206 | |||
| d521699305 | |||
| 5ad23405b7 | |||
| 04cdd807ba | |||
| 646ed50f37 | |||
| 1b0ba0fa68 | |||
| 21c5c919ea | |||
| d9f6f0847d | |||
| 59c897b53e | |||
| 77b32e94f1 | |||
| 331159a3d8 | |||
| 23510c80be | |||
| ef497beaea | |||
| 6ea450a4e1 | |||
| f64786543d | |||
| c60e00c77f | |||
| 7e502beafc | |||
| aa6c30ade6 | |||
| 0b8dfa6878 | |||
| d7bee0c9e0 | |||
| 350c2241c4 | |||
| cfa591838f | |||
| ac1ac0debe | |||
| 54bc60b08f | |||
| f8de46cea9 | |||
| 6fc2a604b4 | |||
| 1e410feed1 | |||
| 948e67a2b7 | |||
| 0f820e4498 | |||
| c7bc1b10e2 | |||
| ef04324f9d | |||
| 2b86865b9b | |||
| f009625c1a | |||
| a991c76842 | |||
| d02ea3244a | |||
| 5a9f87ac63 | |||
| 9bfd0cb63c | |||
| 3f4c19abbc | |||
| cf26635e3c | |||
| e540862401 | |||
| 263be72c11 | |||
| b63c04df4f | |||
| 784208dd87 | |||
| 5cb6c234c4 | |||
| 7972a023ed | |||
| 6cf92c2a90 | |||
| 50835c878e | |||
| b727e5ca1c | |||
| 5773d1fd8d | |||
| 530226dbe8 | |||
| 4882a18bf9 | |||
| 70f3018e67 | |||
| 08c10c1f9b | |||
| 7945eba386 | |||
| b6f4e124ce | |||
| f01e101447 | |||
| 980b81f009 | |||
| 10f0522a6b | |||
| 2bc9cdafb2 | |||
| e643a2ea32 | |||
| b456afa2ac | |||
| 50b55c3f31 | |||
| 88b754e38c | |||
| b3868a77f1 | |||
| 16d5d7b318 | |||
| dbef33fe00 | |||
| 8c11ea69a0 | |||
| fa0ee035e9 | |||
| 010dbf82f3 | |||
| 9a9d27b862 | |||
| 0afd3fc5e1 | |||
| f5ebfcca5a | |||
| 6f33115187 | |||
| 58c65ab48c | |||
| 42294ff695 | |||
| ed59f74cb8 | |||
| d91dee141d | |||
| 2b4438720c | |||
| 758e532a6a | |||
| d4ca6617de | |||
| 67fe7f8d3b | |||
| f6040c5f06 | |||
| 324c384fdf | |||
| de70ee117c | |||
| 85844c8ed4 | |||
| 101dc850a2 | |||
| d88c7c76c6 | |||
| 1fea5f53bd | |||
| ca13109a20 | |||
| 5650de0627 | |||
| 999ee981f7 | |||
| ee6e78927f | |||
| 9dd9ae7a2f | |||
| cbd178dab2 | |||
| e34a28ee53 | |||
| ffcc55b42b | |||
| 72028a9a2e | |||
| d453aa849a | |||
| 9d0cd961b4 | |||
| 900922760a | |||
| 8085e24dcd | |||
| d351ed0173 | |||
| 3ebc5b136c | |||
| a1d02ca689 | |||
| 95546e0a7b | |||
| 103991746b | |||
| 7f0e5dd335 | |||
| fa2fc63b7c | |||
| 4ad915761b | |||
| d6a29c1ad5 | |||
| 3d439c8c01 | |||
| af00374cb0 | |||
| 0bb46aaef4 | |||
| 00377e91b4 | |||
| 477448114a | |||
| 91da1c3f8b | |||
| eddf99ddd0 | |||
| f27580b5ae | |||
| b2d35751b4 | |||
| 18df06a7ce | |||
| 0bee3c9db0 | |||
| e3ba28d767 | |||
| 1f9c2cd181 | |||
| ea2b12a548 | |||
| c9f4db2a4f | |||
| 1e5154c901 | |||
| 17dc41279c | |||
| cb4747aed2 | |||
| ce8d03ddce | |||
| 366c7e2b91 | |||
| 6c5fb5e22b | |||
| 4854bdd02f | |||
| d7ecdc9b07 | |||
| c8d30ae801 | |||
| 9eebe82b34 | |||
| b3915ff8fd | |||
| 805c946b33 | |||
| 8bc1949466 | |||
| a6689b1035 | |||
| f63910d355 | |||
| 98c8521057 | |||
| 57139295b5 | |||
| 79d4dc1646 | |||
| 1760624954 | |||
| ecf9c173c4 | |||
| b0e863dbbb | |||
| 8f8859a5f5 | |||
| 9429657a2b | |||
| 2b0b1e23eb | |||
| dd55baf148 | |||
| 4542157192 | |||
| 9e47172d69 | |||
| 2ad921f99e | |||
| a840969512 | |||
| 67fdfdf584 | |||
| abef8e376c | |||
| 8a85077e70 | |||
| b3ac296cac | |||
| b2db733c83 | |||
| 92dfcf3b6d | |||
| af554fc3f7 | |||
| 02621eee74 | |||
| 8105f51603 | |||
| c40ac27d71 | |||
| d33c930f26 | |||
| eb0fc0fc80 | |||
| a6563543a3 | |||
| 40b20c5595 | |||
| 1ebbfffd2a | |||
| 24d3e09618 | |||
| d92ddc4c7a | |||
| c8b7580830 | |||
| a9d0c052bc | |||
| ad0b349a3f | |||
| 9da51a8d85 | |||
| 814f21a50e | |||
| 092669fb9d | |||
| 3789e1339b | |||
| a1490d07b4 | |||
| 08144382b8 | |||
| cb90e1388f | |||
| 5e1038dc67 | |||
| 5e852ebb5d | |||
| 097786c62d | |||
| baeced32c3 | |||
| 2f4e4343c2 | |||
| 9fd6d7318e | |||
| fd2be958ba | |||
| 719889b27a | |||
| faaa1050da | |||
| 27aed5cd7e | |||
| aa327643f5 | |||
| 3a481df45d | |||
| ac1e405c34 | |||
| 4932473b3f | |||
| 206baf7d80 | |||
| 4ac7a4fdab | |||
| 283e8045d8 | |||
| 8637f4a70d | |||
| 4a768682ea | |||
| dad4f30597 | |||
| 1b81de01b4 | |||
| b09f9e8c12 | |||
| 4b1177e07e | |||
| f714d1ab28 | |||
| dcc9d20cca | |||
| d590ab003f | |||
| a3c52445ee | |||
| be3fcd90e2 | |||
| 26aa336450 | |||
| 65972d9c0c | |||
| d13f47ec0b | |||
| 1e36bc98b9 | |||
| 16dd86e732 | |||
| be894664ef | |||
| f126120421 | |||
| af505b346c | |||
| 77b4689ac8 | |||
| 57118691d8 | |||
| 2b456cbdeb | |||
| 1d3ca5743b | |||
| 5f0a27d355 | |||
| 42d0f342b2 | |||
| c12adea655 | |||
| 2a82e00857 |
@@ -1,9 +1,14 @@
|
|||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: docker
|
- package-ecosystem: docker
|
||||||
directory: "/"
|
directories:
|
||||||
|
- "/"
|
||||||
|
- "/docker"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
|
ignore:
|
||||||
|
- dependency-name: "*"
|
||||||
|
update-types: ["version-update:semver-major"]
|
||||||
open-pull-requests-limit: 10
|
open-pull-requests-limit: 10
|
||||||
|
|
||||||
- package-ecosystem: pip
|
- package-ecosystem: pip
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
run: python build_helpers/binance_update_lev_tiers.py
|
run: python build_helpers/binance_update_lev_tiers.py
|
||||||
|
|
||||||
|
|
||||||
- uses: peter-evans/create-pull-request@v6
|
- uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.REPO_SCOPED_TOKEN }}
|
token: ${{ secrets.REPO_SCOPED_TOKEN }}
|
||||||
add-paths: freqtrade/exchange/binance_leverage_tiers.json
|
add-paths: freqtrade/exchange/binance_leverage_tiers.json
|
||||||
|
|||||||
+62
-30
@@ -25,7 +25,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ "ubuntu-20.04", "ubuntu-22.04", "ubuntu-24.04" ]
|
os: [ "ubuntu-20.04", "ubuntu-22.04", "ubuntu-24.04" ]
|
||||||
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
python-version: ["3.10", "3.11", "3.12"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pip
|
path: ~/.cache/pip
|
||||||
key: test-${{ matrix.os }}-${{ matrix.python-version }}-pip
|
key: pip-${{ matrix.python-version }}-ubuntu
|
||||||
|
|
||||||
- name: TA binary *nix
|
- name: TA binary *nix
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Installation - *nix
|
- name: Installation - *nix
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade "pip<=24.0" 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
|
||||||
@@ -68,11 +68,17 @@ jobs:
|
|||||||
python build_helpers/freqtrade_client_version_align.py
|
python build_helpers/freqtrade_client_version_align.py
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
|
if: (!(runner.os == 'Linux' && matrix.python-version == '3.12' && matrix.os == 'ubuntu-22.04'))
|
||||||
|
run: |
|
||||||
|
pytest --random-order
|
||||||
|
|
||||||
|
- name: Tests with Coveralls
|
||||||
|
if: (runner.os == 'Linux' && matrix.python-version == '3.12' && matrix.os == 'ubuntu-22.04')
|
||||||
run: |
|
run: |
|
||||||
pytest --random-order --cov=freqtrade --cov=freqtrade_client --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.12' && matrix.os == 'ubuntu-22.04')
|
||||||
env:
|
env:
|
||||||
# Coveralls token. Not used as secret due to github not providing secrets to forked repositories
|
# Coveralls token. Not used as secret due to github not providing secrets to forked repositories
|
||||||
COVERALLS_REPO_TOKEN: 6D1m0xupS3FgutfuGao8keFf9Hc0FpIXu
|
COVERALLS_REPO_TOKEN: 6D1m0xupS3FgutfuGao8keFf9Hc0FpIXu
|
||||||
@@ -138,11 +144,8 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ "macos-12", "macos-13", "macos-14" ]
|
os: [ "macos-13", "macos-14", "macos-15" ]
|
||||||
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
python-version: ["3.10", "3.11", "3.12"]
|
||||||
exclude:
|
|
||||||
- os: "macos-14"
|
|
||||||
python-version: "3.9"
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -164,7 +167,7 @@ jobs:
|
|||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/Library/Caches/pip
|
path: ~/Library/Caches/pip
|
||||||
key: ${{ matrix.os }}-${{ matrix.python-version }}-pip
|
key: pip-${{ matrix.os }}-${{ matrix.python-version }}
|
||||||
|
|
||||||
- name: TA binary *nix
|
- name: TA binary *nix
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
@@ -193,11 +196,11 @@ jobs:
|
|||||||
rm /usr/local/bin/python3.11-config || true
|
rm /usr/local/bin/python3.11-config || true
|
||||||
rm /usr/local/bin/python3.12-config || true
|
rm /usr/local/bin/python3.12-config || true
|
||||||
|
|
||||||
brew install hdf5 c-blosc libomp
|
brew install libomp
|
||||||
|
|
||||||
- name: Installation (python)
|
- name: Installation (python)
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade "pip<=24.0" 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
|
||||||
@@ -263,7 +266,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ windows-latest ]
|
os: [ windows-latest ]
|
||||||
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
python-version: ["3.10", "3.11", "3.12"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -273,14 +276,23 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
- name: Pip cache (Windows)
|
- name: Install uv
|
||||||
uses: actions/cache@v4
|
uses: astral-sh/setup-uv@v5
|
||||||
with:
|
with:
|
||||||
path: ~\AppData\Local\pip\Cache
|
enable-cache: true
|
||||||
key: ${{ matrix.os }}-${{ matrix.python-version }}-pip
|
cache-dependency-glob: "requirements**.txt"
|
||||||
|
cache-suffix: "${{ matrix.python-version }}"
|
||||||
|
prune-cache: false
|
||||||
|
|
||||||
- name: Installation
|
- name: Installation
|
||||||
run: |
|
run: |
|
||||||
|
uv venv
|
||||||
|
.venv\Scripts\activate
|
||||||
|
# persist the venv path for future steps
|
||||||
|
"$(pwd)/.venv/Scripts" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
|
function uvpipFunction { uv pip $args }
|
||||||
|
Set-Alias -name pip -value uvpipFunction
|
||||||
./build_helpers/install_windows.ps1
|
./build_helpers/install_windows.ps1
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
@@ -384,7 +396,6 @@ jobs:
|
|||||||
- name: Documentation build
|
- name: Documentation build
|
||||||
run: |
|
run: |
|
||||||
pip install -r docs/requirements-docs.txt
|
pip install -r docs/requirements-docs.txt
|
||||||
pip install mkdocs
|
|
||||||
mkdocs build
|
mkdocs build
|
||||||
|
|
||||||
- name: Discord notification
|
- name: Discord notification
|
||||||
@@ -418,7 +429,7 @@ jobs:
|
|||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pip
|
path: ~/.cache/pip
|
||||||
key: test-${{ matrix.os }}-${{ matrix.python-version }}-pip
|
key: pip-3.12-ubuntu
|
||||||
|
|
||||||
- name: TA binary *nix
|
- name: TA binary *nix
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
@@ -427,7 +438,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Installation - *nix
|
- name: Installation - *nix
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade "pip<=24.0" 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
|
||||||
@@ -515,13 +526,40 @@ jobs:
|
|||||||
ft_client/dist
|
ft_client/dist
|
||||||
retention-days: 10
|
retention-days: 10
|
||||||
|
|
||||||
deploy-pypi:
|
deploy-test-pypi:
|
||||||
name: "Deploy to PyPI"
|
name: "Publish Python 🐍 distribution 📦 to TestPyPI"
|
||||||
needs: [ build ]
|
needs: [ build ]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
if: (github.event_name == 'release')
|
if: (github.event_name == 'release')
|
||||||
environment:
|
environment:
|
||||||
name: release
|
name: testpypi
|
||||||
|
url: https://test.pypi.org/p/freqtrade
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Download artifact 📦
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
pattern: freqtrade*-build
|
||||||
|
path: dist
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Publish to PyPI (Test)
|
||||||
|
uses: pypa/gh-action-pypi-publish@v1.12.3
|
||||||
|
with:
|
||||||
|
repository-url: https://test.pypi.org/legacy/
|
||||||
|
|
||||||
|
|
||||||
|
deploy-pypi:
|
||||||
|
name: "Publish Python 🐍 distribution 📦 to PyPI"
|
||||||
|
needs: [ build ]
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
if: (github.event_name == 'release')
|
||||||
|
environment:
|
||||||
|
name: pypi
|
||||||
url: https://pypi.org/p/freqtrade
|
url: https://pypi.org/p/freqtrade
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
@@ -536,14 +574,8 @@ jobs:
|
|||||||
path: dist
|
path: dist
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
|
|
||||||
- name: Publish to PyPI (Test)
|
|
||||||
uses: pypa/gh-action-pypi-publish@v1.9.0
|
|
||||||
with:
|
|
||||||
repository-url: https://test.pypi.org/legacy/
|
|
||||||
|
|
||||||
- name: Publish to PyPI
|
- name: Publish to PyPI
|
||||||
uses: pypa/gh-action-pypi-publish@v1.9.0
|
uses: pypa/gh-action-pypi-publish@v1.12.3
|
||||||
|
|
||||||
|
|
||||||
deploy-docker:
|
deploy-docker:
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
name: Build Documentation
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
|
||||||
|
# disable permissions for all of the available permissions
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-docs:
|
||||||
|
permissions:
|
||||||
|
contents: write # for mike to push
|
||||||
|
name: Deploy Docs through mike
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.12'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r docs/requirements-docs.txt
|
||||||
|
|
||||||
|
- name: Fetch gh-pages branch
|
||||||
|
run: |
|
||||||
|
git fetch origin gh-pages --depth=1
|
||||||
|
|
||||||
|
- name: Configure Git user
|
||||||
|
run: |
|
||||||
|
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git config --local user.name "github-actions[bot]"
|
||||||
|
|
||||||
|
- name: Build and push Mike
|
||||||
|
if: ${{ github.event_name == 'push' }}
|
||||||
|
run: |
|
||||||
|
mike deploy ${{ github.ref_name }} latest --push --update-aliases
|
||||||
|
|
||||||
|
- name: Build and push Mike - Release
|
||||||
|
if: ${{ github.event_name == 'release' }}
|
||||||
|
run: |
|
||||||
|
mike deploy ${{ github.ref_name }} stable --push --update-aliases
|
||||||
|
|
||||||
|
- name: Show mike versions
|
||||||
|
run: |
|
||||||
|
mike list
|
||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
- name: Run auto-update
|
- name: Run auto-update
|
||||||
run: pre-commit autoupdate
|
run: pre-commit autoupdate
|
||||||
|
|
||||||
- uses: peter-evans/create-pull-request@v6
|
- uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.REPO_SCOPED_TOKEN }}
|
token: ${{ secrets.REPO_SCOPED_TOKEN }}
|
||||||
add-paths: .pre-commit-config.yaml
|
add-paths: .pre-commit-config.yaml
|
||||||
|
|||||||
@@ -114,3 +114,5 @@ target/
|
|||||||
!config_examples/config_full.example.json
|
!config_examples/config_full.example.json
|
||||||
!config_examples/config_kraken.example.json
|
!config_examples/config_kraken.example.json
|
||||||
!config_examples/config_freqai.example.json
|
!config_examples/config_freqai.example.json
|
||||||
|
|
||||||
|
docker-compose-*.yml
|
||||||
|
|||||||
+15
-9
@@ -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: "7.1.0"
|
rev: "7.1.1"
|
||||||
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.11.0"
|
rev: "v1.14.0"
|
||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
exclude: build_helpers
|
exclude: build_helpers
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
- types-cachetools==5.4.0.20240717
|
- types-cachetools==5.5.0.20240820
|
||||||
- types-filelock==3.2.7
|
- types-filelock==3.2.7
|
||||||
- types-requests==2.32.0.20240712
|
- types-requests==2.32.0.20241016
|
||||||
- types-tabulate==0.9.0.20240106
|
- types-tabulate==0.9.0.20241207
|
||||||
- types-python-dateutil==2.9.0.20240316
|
- types-python-dateutil==2.9.0.20241206
|
||||||
- SQLAlchemy==2.0.31
|
- SQLAlchemy==2.0.36
|
||||||
# stages: [push]
|
# stages: [push]
|
||||||
|
|
||||||
- repo: https://github.com/pycqa/isort
|
- repo: https://github.com/pycqa/isort
|
||||||
@@ -31,12 +31,13 @@ repos:
|
|||||||
|
|
||||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: 'v0.5.4'
|
rev: 'v0.8.4'
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
|
- id: ruff-format
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.6.0
|
rev: v5.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
exclude: |
|
exclude: |
|
||||||
@@ -55,6 +56,11 @@ repos:
|
|||||||
.*\.md
|
.*\.md
|
||||||
)$
|
)$
|
||||||
|
|
||||||
|
- repo: https://github.com/stefmolin/exif-stripper
|
||||||
|
rev: 0.6.1
|
||||||
|
hooks:
|
||||||
|
- id: strip-exif
|
||||||
|
|
||||||
- repo: https://github.com/codespell-project/codespell
|
- repo: https://github.com/codespell-project/codespell
|
||||||
rev: v2.3.0
|
rev: v2.3.0
|
||||||
hooks:
|
hooks:
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
FROM python:3.12.4-slim-bookworm as base
|
FROM python:3.12.7-slim-bookworm as base
|
||||||
|
|
||||||
# Setup env
|
# Setup env
|
||||||
ENV LANG C.UTF-8
|
ENV LANG C.UTF-8
|
||||||
@@ -25,7 +25,7 @@ FROM base as python-deps
|
|||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get -y install build-essential libssl-dev git libffi-dev libgfortran5 pkg-config cmake gcc \
|
&& apt-get -y install build-essential libssl-dev git libffi-dev libgfortran5 pkg-config cmake gcc \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& pip install --upgrade "pip<=24.0" wheel
|
&& pip install --upgrade pip wheel
|
||||||
|
|
||||||
# Install TA-lib
|
# Install TA-lib
|
||||||
COPY build_helpers/* /tmp/
|
COPY build_helpers/* /tmp/
|
||||||
|
|||||||
@@ -30,16 +30,20 @@ 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] [BingX](https://bingx.com/invite/0EM9RX)
|
- [X] [BingX](https://bingx.com/invite/0EM9RX)
|
||||||
|
- [X] [Bybit](https://bybit.com/)
|
||||||
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
||||||
- [X] [HTX](https://www.htx.com/) (Former Huobi)
|
- [X] [HTX](https://www.htx.com/)
|
||||||
|
- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX)
|
||||||
- [X] [Kraken](https://kraken.com/)
|
- [X] [Kraken](https://kraken.com/)
|
||||||
- [X] [OKX](https://okx.com/) (Former OKEX)
|
- [X] [OKX](https://okx.com/)
|
||||||
|
- [X] [MyOKX](https://okx.com/) (OKX EEA)
|
||||||
- [ ] [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)_
|
||||||
|
|
||||||
### Supported Futures Exchanges (experimental)
|
### Supported Futures Exchanges (experimental)
|
||||||
|
|
||||||
- [X] [Binance](https://www.binance.com/)
|
- [X] [Binance](https://www.binance.com/)
|
||||||
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
||||||
|
- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX)
|
||||||
- [X] [OKX](https://okx.com/)
|
- [X] [OKX](https://okx.com/)
|
||||||
- [X] [Bybit](https://bybit.com/)
|
- [X] [Bybit](https://bybit.com/)
|
||||||
|
|
||||||
@@ -60,7 +64,7 @@ Please find the complete documentation on the [freqtrade website](https://www.fr
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- [x] **Based on Python 3.9+**: For botting on any operating system - Windows, macOS and Linux.
|
- [x] **Based on Python 3.10+**: For botting on any operating system - Windows, macOS and Linux.
|
||||||
- [x] **Persistence**: Persistence is achieved through sqlite.
|
- [x] **Persistence**: Persistence is achieved through sqlite.
|
||||||
- [x] **Dry-run**: Run the bot without paying money.
|
- [x] **Dry-run**: Run the bot without paying money.
|
||||||
- [x] **Backtesting**: Run a simulation of your buy/sell strategy.
|
- [x] **Backtesting**: Run a simulation of your buy/sell strategy.
|
||||||
@@ -86,41 +90,50 @@ For further (native) installation methods, please refer to the [Installation doc
|
|||||||
|
|
||||||
```
|
```
|
||||||
usage: freqtrade [-h] [-V]
|
usage: freqtrade [-h] [-V]
|
||||||
{trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver}
|
{trade,create-userdir,new-config,show-config,new-strategy,download-data,convert-data,convert-trade-data,trades-to-ohlcv,list-data,backtesting,backtesting-show,backtesting-analysis,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-markets,list-pairs,list-strategies,list-freqaimodels,list-timeframes,show-trades,test-pairlist,convert-db,install-ui,plot-dataframe,plot-profit,webserver,strategy-updater,lookahead-analysis,recursive-analysis}
|
||||||
...
|
...
|
||||||
|
|
||||||
Free, open source crypto trading bot
|
Free, open source crypto trading bot
|
||||||
|
|
||||||
positional arguments:
|
positional arguments:
|
||||||
{trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver}
|
{trade,create-userdir,new-config,show-config,new-strategy,download-data,convert-data,convert-trade-data,trades-to-ohlcv,list-data,backtesting,backtesting-show,backtesting-analysis,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-markets,list-pairs,list-strategies,list-freqaimodels,list-timeframes,show-trades,test-pairlist,convert-db,install-ui,plot-dataframe,plot-profit,webserver,strategy-updater,lookahead-analysis,recursive-analysis}
|
||||||
trade Trade module.
|
trade Trade module.
|
||||||
create-userdir Create user-data directory.
|
create-userdir Create user-data directory.
|
||||||
new-config Create new config
|
new-config Create new config
|
||||||
|
show-config Show resolved config
|
||||||
new-strategy Create new strategy
|
new-strategy Create new strategy
|
||||||
download-data Download backtesting data.
|
download-data Download backtesting data.
|
||||||
convert-data Convert candle (OHLCV) data from one format to
|
convert-data Convert candle (OHLCV) data from one format to
|
||||||
another.
|
another.
|
||||||
convert-trade-data Convert trade data from one format to another.
|
convert-trade-data Convert trade data from one format to another.
|
||||||
|
trades-to-ohlcv Convert trade data to OHLCV data.
|
||||||
list-data List downloaded data.
|
list-data List downloaded data.
|
||||||
backtesting Backtesting module.
|
backtesting Backtesting module.
|
||||||
|
backtesting-show Show past Backtest results
|
||||||
|
backtesting-analysis
|
||||||
|
Backtest Analysis module.
|
||||||
edge Edge module.
|
edge Edge module.
|
||||||
hyperopt Hyperopt module.
|
hyperopt Hyperopt module.
|
||||||
hyperopt-list List Hyperopt results
|
hyperopt-list List Hyperopt results
|
||||||
hyperopt-show Show details of Hyperopt results
|
hyperopt-show Show details of Hyperopt results
|
||||||
list-exchanges Print available exchanges.
|
list-exchanges Print available exchanges.
|
||||||
list-hyperopts Print available hyperopt classes.
|
|
||||||
list-markets Print markets on exchange.
|
list-markets Print markets on exchange.
|
||||||
list-pairs Print pairs on exchange.
|
list-pairs Print pairs on exchange.
|
||||||
list-strategies Print available strategies.
|
list-strategies Print available strategies.
|
||||||
|
list-freqaimodels Print available freqAI models.
|
||||||
list-timeframes Print available timeframes for the exchange.
|
list-timeframes Print available timeframes for the exchange.
|
||||||
show-trades Show trades.
|
show-trades Show trades.
|
||||||
test-pairlist Test your pairlist configuration.
|
test-pairlist Test your pairlist configuration.
|
||||||
|
convert-db Migrate database to different system
|
||||||
install-ui Install FreqUI
|
install-ui Install FreqUI
|
||||||
plot-dataframe Plot candles with indicators.
|
plot-dataframe Plot candles with indicators.
|
||||||
plot-profit Generate plot showing profits.
|
plot-profit Generate plot showing profits.
|
||||||
webserver Webserver module.
|
webserver Webserver module.
|
||||||
|
strategy-updater updates outdated strategy files to the current version
|
||||||
|
lookahead-analysis Check for potential look ahead bias.
|
||||||
|
recursive-analysis Check for potential recursive formula issue.
|
||||||
|
|
||||||
optional arguments:
|
options:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
-V, --version show program's version number and exit
|
-V, --version show program's version number and exit
|
||||||
|
|
||||||
@@ -208,7 +221,7 @@ To run this bot we recommend you a cloud instance with a minimum of:
|
|||||||
|
|
||||||
### Software requirements
|
### Software requirements
|
||||||
|
|
||||||
- [Python >= 3.9](http://docs.python-guide.org/en/latest/starting/installation/)
|
- [Python >= 3.10](http://docs.python-guide.org/en/latest/starting/installation/)
|
||||||
- [pip](https://pip.pypa.io/en/stable/installing/)
|
- [pip](https://pip.pypa.io/en/stable/installing/)
|
||||||
- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
||||||
- [TA-Lib](https://ta-lib.github.io/ta-lib-python/)
|
- [TA-Lib](https://ta-lib.github.io/ta-lib-python/)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,11 +1,10 @@
|
|||||||
# vendored Wheels compiled via https://github.com/xmatthias/ta-lib-python/tree/ta_bundled_040
|
# vendored Wheels compiled via https://github.com/xmatthias/ta-lib-python/tree/ta_bundled_040
|
||||||
|
|
||||||
python -m pip install --upgrade "pip<=24.0" wheel
|
python -m pip install --upgrade pip
|
||||||
|
python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
|
||||||
|
|
||||||
$pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
|
pip install -U wheel "numpy<2"
|
||||||
|
pip install --only-binary ta-lib --find-links=build_helpers\ ta-lib
|
||||||
|
|
||||||
pip install --find-links=build_helpers\ --prefer-binary TA-Lib
|
|
||||||
|
|
||||||
pip install -r requirements-dev.txt
|
pip install -r requirements-dev.txt
|
||||||
pip install -e .
|
pip install -e .
|
||||||
|
|||||||
BIN
Binary file not shown.
+55
-61
@@ -9,11 +9,6 @@
|
|||||||
],
|
],
|
||||||
"minimum": -1
|
"minimum": -1
|
||||||
},
|
},
|
||||||
"new_pairs_days": {
|
|
||||||
"description": "Download data of new pairs for given number of days",
|
|
||||||
"type": "integer",
|
|
||||||
"default": 30
|
|
||||||
},
|
|
||||||
"timeframe": {
|
"timeframe": {
|
||||||
"description": "The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). \nUsually specified in the strategy and missing in the configuration.",
|
"description": "The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). \nUsually specified in the strategy and missing in the configuration.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -107,8 +102,17 @@
|
|||||||
},
|
},
|
||||||
"dry_run_wallet": {
|
"dry_run_wallet": {
|
||||||
"description": "Initial wallet balance for dry run mode.",
|
"description": "Initial wallet balance for dry run mode.",
|
||||||
"type": "number",
|
"type": [
|
||||||
"default": 1000
|
"number",
|
||||||
|
"object"
|
||||||
|
],
|
||||||
|
"default": 1000,
|
||||||
|
"patternProperties": {
|
||||||
|
"^[a-zA-Z0-9]+$": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
"cancel_open_orders_on_exit": {
|
"cancel_open_orders_on_exit": {
|
||||||
"description": "Cancel open orders when exiting.",
|
"description": "Cancel open orders when exiting.",
|
||||||
@@ -562,6 +566,7 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"StaticPairList",
|
"StaticPairList",
|
||||||
"VolumePairList",
|
"VolumePairList",
|
||||||
|
"PercentChangePairList",
|
||||||
"ProducerPairList",
|
"ProducerPairList",
|
||||||
"RemotePairList",
|
"RemotePairList",
|
||||||
"MarketCapPairList",
|
"MarketCapPairList",
|
||||||
@@ -583,53 +588,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"protections": {
|
|
||||||
"description": "Configuration for various protections.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"method": {
|
|
||||||
"description": "Method used for the protection.",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"CooldownPeriod",
|
|
||||||
"LowProfitPairs",
|
|
||||||
"MaxDrawdown",
|
|
||||||
"StoplossGuard"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"stop_duration": {
|
|
||||||
"description": "Duration to lock the pair after a protection is triggered, in minutes.",
|
|
||||||
"type": "number",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"stop_duration_candles": {
|
|
||||||
"description": "Duration to lock the pair after a protection is triggered, in number of candles.",
|
|
||||||
"type": "number",
|
|
||||||
"minimum": 0
|
|
||||||
},
|
|
||||||
"trade_limit": {
|
|
||||||
"description": "Minimum number of trades required during lookback period.",
|
|
||||||
"type": "number",
|
|
||||||
"minimum": 1
|
|
||||||
},
|
|
||||||
"lookback_period": {
|
|
||||||
"description": "Period to look back for protection checks, in minutes.",
|
|
||||||
"type": "number",
|
|
||||||
"minimum": 1
|
|
||||||
},
|
|
||||||
"lookback_period_candles": {
|
|
||||||
"description": "Period to look back for protection checks, in number of candles.",
|
|
||||||
"type": "number",
|
|
||||||
"minimum": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"method"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"telegram": {
|
"telegram": {
|
||||||
"description": "Telegram settings.",
|
"description": "Telegram settings.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -643,7 +601,11 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"chat_id": {
|
"chat_id": {
|
||||||
"description": "Telegram chat ID",
|
"description": "Telegram chat or group ID",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"topic_id": {
|
||||||
|
"description": "Telegram topic ID - only applicable for group chats",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"allow_custom_messages": {
|
"allow_custom_messages": {
|
||||||
@@ -733,12 +695,18 @@
|
|||||||
},
|
},
|
||||||
"exit_fill": {
|
"exit_fill": {
|
||||||
"description": "Telegram setting for exit fill signals.",
|
"description": "Telegram setting for exit fill signals.",
|
||||||
"type": "string",
|
"type": [
|
||||||
"enum": [
|
"string",
|
||||||
"on",
|
"object"
|
||||||
"off",
|
|
||||||
"silent"
|
|
||||||
],
|
],
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"on",
|
||||||
|
"off",
|
||||||
|
"silent"
|
||||||
|
]
|
||||||
|
},
|
||||||
"default": "on"
|
"default": "on"
|
||||||
},
|
},
|
||||||
"exit_cancel": {
|
"exit_cancel": {
|
||||||
@@ -1064,7 +1032,7 @@
|
|||||||
"default": {},
|
"default": {},
|
||||||
"properties": {
|
"properties": {
|
||||||
"process_throttle_secs": {
|
"process_throttle_secs": {
|
||||||
"description": "Throttle time in seconds for processing.",
|
"description": "Minimum loop duration for one bot iteration in seconds.",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"interval": {
|
"interval": {
|
||||||
@@ -1105,6 +1073,15 @@
|
|||||||
"description": "Enable position adjustment. \nUsually specified in the strategy and missing in the configuration.",
|
"description": "Enable position adjustment. \nUsually specified in the strategy and missing in the configuration.",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"new_pairs_days": {
|
||||||
|
"description": "Download data of new pairs for given number of days",
|
||||||
|
"type": "integer",
|
||||||
|
"default": 30
|
||||||
|
},
|
||||||
|
"download_trades": {
|
||||||
|
"description": "Download trades data by default (instead of ohlcv data).",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"max_entry_position_adjustment": {
|
"max_entry_position_adjustment": {
|
||||||
"description": "Maximum entry position adjustment allowed. \nUsually specified in the strategy and missing in the configuration.",
|
"description": "Maximum entry position adjustment allowed. \nUsually specified in the strategy and missing in the configuration.",
|
||||||
"type": [
|
"type": [
|
||||||
@@ -1113,6 +1090,13 @@
|
|||||||
],
|
],
|
||||||
"minimum": -1
|
"minimum": -1
|
||||||
},
|
},
|
||||||
|
"add_config_files": {
|
||||||
|
"description": "Additional configuration files to load.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
"orderflow": {
|
"orderflow": {
|
||||||
"description": "Settings related to order flow.",
|
"description": "Settings related to order flow.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -1208,6 +1192,11 @@
|
|||||||
},
|
},
|
||||||
"uniqueItems": true
|
"uniqueItems": true
|
||||||
},
|
},
|
||||||
|
"log_responses": {
|
||||||
|
"description": "Log responses from the exchange.Useful/required to debug issues with order processing.",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
"unknown_fee_rate": {
|
"unknown_fee_rate": {
|
||||||
"description": "Fee rate for unknown markets.",
|
"description": "Fee rate for unknown markets.",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
@@ -1413,6 +1402,11 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "example"
|
"default": "example"
|
||||||
},
|
},
|
||||||
|
"wait_for_training_iteration_on_reload": {
|
||||||
|
"description": "Wait for the next training iteration to complete after /reload or ctrl+c.",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
"feature_parameters": {
|
"feature_parameters": {
|
||||||
"description": "The parameters used to engineer the feature set",
|
"description": "The parameters used to engineer the feature set",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"$schema": "https://schema.freqtrade.io/schema.json",
|
||||||
"max_open_trades": 3,
|
"max_open_trades": 3,
|
||||||
"stake_currency": "USDT",
|
"stake_currency": "USDT",
|
||||||
"stake_amount": 0.05,
|
"stake_amount": 0.05,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"$schema": "https://schema.freqtrade.io/schema.json",
|
||||||
"trading_mode": "futures",
|
"trading_mode": "futures",
|
||||||
"margin_mode": "isolated",
|
"margin_mode": "isolated",
|
||||||
"max_open_trades": 5,
|
"max_open_trades": 5,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"$schema": "https://schema.freqtrade.io/schema.json",
|
||||||
"max_open_trades": 3,
|
"max_open_trades": 3,
|
||||||
"stake_currency": "BTC",
|
"stake_currency": "BTC",
|
||||||
"stake_amount": 0.05,
|
"stake_amount": 0.05,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"$schema": "https://schema.freqtrade.io/schema.json",
|
||||||
"max_open_trades": 5,
|
"max_open_trades": 5,
|
||||||
"stake_currency": "EUR",
|
"stake_currency": "EUR",
|
||||||
"stake_amount": 10,
|
"stake_amount": 10,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM python:3.11.8-slim-bookworm as base
|
FROM python:3.11.10-slim-bookworm as base
|
||||||
|
|
||||||
# Setup env
|
# Setup env
|
||||||
ENV LANG C.UTF-8
|
ENV LANG C.UTF-8
|
||||||
@@ -17,7 +17,7 @@ RUN mkdir /freqtrade \
|
|||||||
&& chown ftuser:ftuser /freqtrade \
|
&& chown ftuser:ftuser /freqtrade \
|
||||||
# Allow sudoers
|
# Allow sudoers
|
||||||
&& echo "ftuser ALL=(ALL) NOPASSWD: /bin/chown" >> /etc/sudoers \
|
&& echo "ftuser ALL=(ALL) NOPASSWD: /bin/chown" >> /etc/sudoers \
|
||||||
&& pip install --upgrade "pip<=24.0"
|
&& pip install --upgrade pip
|
||||||
|
|
||||||
WORKDIR /freqtrade
|
WORKDIR /freqtrade
|
||||||
|
|
||||||
|
|||||||
@@ -18,15 +18,13 @@ freqtrade backtesting -c <config.json> --timeframe <tf> --strategy <strategy_nam
|
|||||||
```
|
```
|
||||||
|
|
||||||
This will tell freqtrade to output a pickled dictionary of strategy, pairs and corresponding
|
This will tell freqtrade to output a pickled dictionary of strategy, pairs and corresponding
|
||||||
DataFrame of the candles that resulted in buy signals. Depending on how many buys your strategy
|
DataFrame of the candles that resulted in entry and exit signals.
|
||||||
makes, this file may get quite large, so periodically check your `user_data/backtest_results`
|
Depending on how many entries your strategy makes, this file may get quite large, so periodically check your `user_data/backtest_results` folder to delete old exports.
|
||||||
folder to delete old exports.
|
|
||||||
|
|
||||||
Before running your next backtest, make sure you either delete your old backtest results or run
|
Before running your next backtest, make sure you either delete your old backtest results or run
|
||||||
backtesting with the `--cache none` option to make sure no cached results are used.
|
backtesting with the `--cache none` option to make sure no cached results are used.
|
||||||
|
|
||||||
If all goes well, you should now see a `backtest-result-{timestamp}_signals.pkl` file in the
|
If all goes well, you should now see a `backtest-result-{timestamp}_signals.pkl` and `backtest-result-{timestamp}_exited.pkl` files in the `user_data/backtest_results` folder.
|
||||||
`user_data/backtest_results` folder.
|
|
||||||
|
|
||||||
To analyze the entry/exit tags, we now need to use the `freqtrade backtesting-analysis` command
|
To analyze the entry/exit tags, we now need to use the `freqtrade backtesting-analysis` command
|
||||||
with `--analysis-groups` option provided with space-separated arguments:
|
with `--analysis-groups` option provided with space-separated arguments:
|
||||||
@@ -103,6 +101,10 @@ The indicators have to be present in your strategy's main DataFrame (either for
|
|||||||
timeframe or for informative timeframes) otherwise they will simply be ignored in the script
|
timeframe or for informative timeframes) otherwise they will simply be ignored in the script
|
||||||
output.
|
output.
|
||||||
|
|
||||||
|
!!! Note "Indicator List"
|
||||||
|
The indicator values will be displayed for both entry and exit points. If `--indicator-list all` is specified,
|
||||||
|
only the indicators at the entry point will be shown to avoid excessively large lists, which could occur depending on the strategy.
|
||||||
|
|
||||||
There are a range of candle and trade-related fields that are included in the analysis so are
|
There are a range of candle and trade-related fields that are included in the analysis so are
|
||||||
automatically accessible by including them on the indicator-list, and these include:
|
automatically accessible by including them on the indicator-list, and these include:
|
||||||
|
|
||||||
@@ -118,6 +120,53 @@ automatically accessible by including them on the indicator-list, and these incl
|
|||||||
- **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
|
||||||
|
|
||||||
|
#### Sample Output for Indicator Values
|
||||||
|
|
||||||
|
```bash
|
||||||
|
freqtrade backtesting-analysis -c user_data/config.json --analysis-groups 0 --indicator-list chikou_span tenkan_sen
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example,
|
||||||
|
we aim to display the `chikou_span` and `tenkan_sen` indicator values at both the entry and exit points of trades.
|
||||||
|
|
||||||
|
A sample output for indicators might look like this:
|
||||||
|
|
||||||
|
| pair | open_date | enter_reason | exit_reason | chikou_span (entry) | tenkan_sen (entry) | chikou_span (exit) | tenkan_sen (exit) |
|
||||||
|
|-----------|---------------------------|--------------|-------------|---------------------|--------------------|--------------------|-------------------|
|
||||||
|
| DOGE/USDT | 2024-07-06 00:35:00+00:00 | | exit_signal | 0.105 | 0.106 | 0.105 | 0.107 |
|
||||||
|
| BTC/USDT | 2024-08-05 14:20:00+00:00 | | roi | 54643.440 | 51696.400 | 54386.000 | 52072.010 |
|
||||||
|
|
||||||
|
As shown in the table, `chikou_span (entry)` represents the indicator value at the time of trade entry,
|
||||||
|
while `chikou_span (exit)` reflects its value at the time of exit.
|
||||||
|
This detailed view of indicator values enhances the analysis.
|
||||||
|
|
||||||
|
The `(entry)` and `(exit)` suffixes are added to indicators
|
||||||
|
to distinguish the values at the entry and exit points of the trade.
|
||||||
|
|
||||||
|
!!! Note "Trade-wide Indicators"
|
||||||
|
Certain trade-wide indicators do not have the `(entry)` or `(exit)` suffix. These indicators include: `pair`, `stake_amount`,
|
||||||
|
`max_stake_amount`, `amount`, `open_date`, `close_date`, `open_rate`, `close_rate`, `fee_open`, `fee_close`, `trade_duration`,
|
||||||
|
`profit_ratio`, `profit_abs`, `exit_reason`,`initial_stop_loss_abs`, `initial_stop_loss_ratio`, `stop_loss_abs`, `stop_loss_ratio`,
|
||||||
|
`min_rate`, `max_rate`, `is_open`, `enter_tag`, `leverage`, `is_short`, `open_timestamp`, `close_timestamp` and `orders`
|
||||||
|
|
||||||
|
#### Filtering Indicators Based on Entry or Exit Signals
|
||||||
|
|
||||||
|
The `--indicator-list` option, by default, displays indicator values for both entry and exit signals. To filter the indicator values exclusively for entry signals, you can use the `--entry-only` argument. Similarly, to display indicator values only at exit signals, use the `--exit-only` argument.
|
||||||
|
|
||||||
|
Example: Display indicator values at entry signals:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
freqtrade backtesting-analysis -c user_data/config.json --analysis-groups 0 --indicator-list chikou_span tenkan_sen --entry-only
|
||||||
|
```
|
||||||
|
|
||||||
|
Example: Display indicator values at exit signals:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
freqtrade backtesting-analysis -c user_data/config.json --analysis-groups 0 --indicator-list chikou_span tenkan_sen --exit-only
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
When using these filters, the indicator names will not be suffixed with `(entry)` or `(exit)`.
|
||||||
|
|
||||||
### Filtering the trade output by date
|
### Filtering the trade output by date
|
||||||
|
|
||||||
|
|||||||
@@ -30,11 +30,18 @@ class SuperDuperHyperOptLoss(IHyperOptLoss):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def hyperopt_loss_function(results: DataFrame, trade_count: int,
|
def hyperopt_loss_function(
|
||||||
min_date: datetime, max_date: datetime,
|
*,
|
||||||
config: Config, processed: Dict[str, DataFrame],
|
results: DataFrame,
|
||||||
backtest_stats: Dict[str, Any],
|
trade_count: int,
|
||||||
*args, **kwargs) -> float:
|
min_date: datetime,
|
||||||
|
max_date: datetime,
|
||||||
|
config: Config,
|
||||||
|
processed: dict[str, DataFrame],
|
||||||
|
backtest_stats: dict[str, Any],
|
||||||
|
starting_balance: float,
|
||||||
|
**kwargs,
|
||||||
|
) -> float:
|
||||||
"""
|
"""
|
||||||
Objective function, returns smaller number for better results
|
Objective function, returns smaller number for better results
|
||||||
This is the legacy algorithm (used until now in freqtrade).
|
This is the legacy algorithm (used until now in freqtrade).
|
||||||
@@ -64,6 +71,7 @@ Currently, the arguments are:
|
|||||||
* `config`: Config object used (Note: Not all strategy-related parameters will be updated here if they are part of a hyperopt space).
|
* `config`: Config object used (Note: Not all strategy-related parameters will be updated here if they are part of a hyperopt space).
|
||||||
* `processed`: Dict of Dataframes with the pair as keys containing the data used for backtesting.
|
* `processed`: Dict of Dataframes with the pair as keys containing the data used for backtesting.
|
||||||
* `backtest_stats`: Backtesting statistics using the same format as the backtesting file "strategy" substructure. Available fields can be seen in `generate_strategy_stats()` in `optimize_reports.py`.
|
* `backtest_stats`: Backtesting statistics using the same format as the backtesting file "strategy" substructure. Available fields can be seen in `generate_strategy_stats()` in `optimize_reports.py`.
|
||||||
|
* `starting_balance`: Starting balance used for backtesting.
|
||||||
|
|
||||||
This function needs to return a floating point number (`float`). Smaller numbers will be interpreted as better results. The parameters and balancing for this is up to you.
|
This function needs to return a floating point number (`float`). Smaller numbers will be interpreted as better results. The parameters and balancing for this is up to you.
|
||||||
|
|
||||||
@@ -97,7 +105,7 @@ class MyAwesomeStrategy(IStrategy):
|
|||||||
SKDecimal(0.01, 0.20, decimals=3, name='roi_p3'),
|
SKDecimal(0.01, 0.20, decimals=3, name='roi_p3'),
|
||||||
]
|
]
|
||||||
|
|
||||||
def generate_roi_table(params: Dict) -> Dict[int, float]:
|
def generate_roi_table(params: Dict) -> dict[int, float]:
|
||||||
|
|
||||||
roi_table = {}
|
roi_table = {}
|
||||||
roi_table[0] = params['roi_p1'] + params['roi_p2'] + params['roi_p3']
|
roi_table[0] = params['roi_p1'] + params['roi_p2'] + params['roi_p3']
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ This guide walks you through utilizing public trade data for advanced orderflow
|
|||||||
|
|
||||||
!!! Warning "Experimental Feature"
|
!!! Warning "Experimental Feature"
|
||||||
The orderflow feature is currently in beta and may be subject to changes in future releases. Please report any issues or feedback on the [Freqtrade GitHub repository](https://github.com/freqtrade/freqtrade/issues).
|
The orderflow feature is currently in beta and may be subject to changes in future releases. Please report any issues or feedback on the [Freqtrade GitHub repository](https://github.com/freqtrade/freqtrade/issues).
|
||||||
|
It's also currently not been tested with freqAI - and combining these two features is considered out of scope at this point.
|
||||||
|
|
||||||
!!! Warning "Performance"
|
!!! Warning "Performance"
|
||||||
Orderflow requires raw trades data. This data is rather large, and can cause a slow initial startup, when freqtrade needs to download the trades data for the last X candles. Additionally, enabling this feature will cause increased memory usage. Please ensure to have sufficient resources available.
|
Orderflow requires raw trades data. This data is rather large, and can cause a slow initial startup, when freqtrade needs to download the trades data for the last X candles. Additionally, enabling this feature will cause increased memory usage. Please ensure to have sufficient resources available.
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 51 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 12 KiB |
+26
-14
@@ -10,12 +10,14 @@ To learn how to get data for the pairs and exchange you're interested in, head o
|
|||||||
```
|
```
|
||||||
usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
||||||
[-d PATH] [--userdir PATH] [-s NAME]
|
[-d PATH] [--userdir PATH] [-s NAME]
|
||||||
[--strategy-path PATH] [-i TIMEFRAME]
|
[--strategy-path PATH]
|
||||||
[--timerange TIMERANGE]
|
[--recursive-strategy-search]
|
||||||
[--data-format-ohlcv {json,jsongz,hdf5}]
|
[--freqaimodel NAME] [--freqaimodel-path PATH]
|
||||||
|
[-i TIMEFRAME] [--timerange TIMERANGE]
|
||||||
|
[--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}]
|
||||||
[--max-open-trades INT]
|
[--max-open-trades INT]
|
||||||
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
|
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
|
||||||
[-p PAIRS [PAIRS ...]] [--eps] [--dmmp]
|
[-p PAIRS [PAIRS ...]] [--eps]
|
||||||
[--enable-protections]
|
[--enable-protections]
|
||||||
[--dry-run-wallet DRY_RUN_WALLET]
|
[--dry-run-wallet DRY_RUN_WALLET]
|
||||||
[--timeframe-detail TIMEFRAME_DETAIL]
|
[--timeframe-detail TIMEFRAME_DETAIL]
|
||||||
@@ -24,8 +26,9 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
|||||||
[--export-filename PATH]
|
[--export-filename PATH]
|
||||||
[--breakdown {day,week,month} [{day,week,month} ...]]
|
[--breakdown {day,week,month} [{day,week,month} ...]]
|
||||||
[--cache {none,day,week,month}]
|
[--cache {none,day,week,month}]
|
||||||
|
[--freqai-backtest-live-models]
|
||||||
|
|
||||||
optional arguments:
|
options:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
-i TIMEFRAME, --timeframe TIMEFRAME
|
-i TIMEFRAME, --timeframe TIMEFRAME
|
||||||
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
|
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
|
||||||
@@ -48,10 +51,6 @@ optional arguments:
|
|||||||
--eps, --enable-position-stacking
|
--eps, --enable-position-stacking
|
||||||
Allow buying the same pair multiple times (position
|
Allow buying the same pair multiple times (position
|
||||||
stacking).
|
stacking).
|
||||||
--dmmp, --disable-max-market-positions
|
|
||||||
Disable applying `max_open_trades` during backtest
|
|
||||||
(same as setting `max_open_trades` to a very high
|
|
||||||
number).
|
|
||||||
--enable-protections, --enableprotections
|
--enable-protections, --enableprotections
|
||||||
Enable protections for backtesting.Will slow
|
Enable protections for backtesting.Will slow
|
||||||
backtesting down by a considerable amount, but will
|
backtesting down by a considerable amount, but will
|
||||||
@@ -80,10 +79,13 @@ optional arguments:
|
|||||||
--cache {none,day,week,month}
|
--cache {none,day,week,month}
|
||||||
Load a cached backtest result no older than specified
|
Load a cached backtest result no older than specified
|
||||||
age (default: day).
|
age (default: day).
|
||||||
|
--freqai-backtest-live-models
|
||||||
|
Run backtest with ready models.
|
||||||
|
|
||||||
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).
|
||||||
--logfile FILE Log to the file specified. Special values are:
|
--logfile FILE, --log-file FILE
|
||||||
|
Log to the file specified. Special values are:
|
||||||
'syslog', 'journald'. See the documentation for more
|
'syslog', 'journald'. See the documentation for more
|
||||||
details.
|
details.
|
||||||
-V, --version show program's version number and exit
|
-V, --version show program's version number and exit
|
||||||
@@ -92,7 +94,7 @@ Common arguments:
|
|||||||
`userdir/config.json` or `config.json` whichever
|
`userdir/config.json` or `config.json` whichever
|
||||||
exists). Multiple --config options may be used. Can be
|
exists). Multiple --config options may be used. Can be
|
||||||
set to `-` to read config from stdin.
|
set to `-` to read config from stdin.
|
||||||
-d PATH, --datadir PATH
|
-d PATH, --datadir PATH, --data-dir PATH
|
||||||
Path to directory with historical backtesting data.
|
Path to directory with historical backtesting data.
|
||||||
--userdir PATH, --user-data-dir PATH
|
--userdir PATH, --user-data-dir PATH
|
||||||
Path to userdata directory.
|
Path to userdata directory.
|
||||||
@@ -102,6 +104,12 @@ Strategy arguments:
|
|||||||
Specify strategy class name which will be used by the
|
Specify strategy class name which will be used by the
|
||||||
bot.
|
bot.
|
||||||
--strategy-path PATH Specify additional strategy lookup path.
|
--strategy-path PATH Specify additional strategy lookup path.
|
||||||
|
--recursive-strategy-search
|
||||||
|
Recursively search for a strategy in the strategies
|
||||||
|
folder.
|
||||||
|
--freqaimodel NAME Specify a custom freqaimodels.
|
||||||
|
--freqaimodel-path PATH
|
||||||
|
Specify additional lookup path for freqaimodels.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -293,6 +301,7 @@ A backtesting result will look like that:
|
|||||||
|-----------------------------+---------------------|
|
|-----------------------------+---------------------|
|
||||||
| Backtesting from | 2019-01-01 00:00:00 |
|
| Backtesting from | 2019-01-01 00:00:00 |
|
||||||
| Backtesting to | 2019-05-01 00:00:00 |
|
| Backtesting to | 2019-05-01 00:00:00 |
|
||||||
|
| Trading Mode | Spot |
|
||||||
| Max open trades | 3 |
|
| Max open trades | 3 |
|
||||||
| | |
|
| | |
|
||||||
| Total/Daily Avg Trades | 429 / 3.575 |
|
| Total/Daily Avg Trades | 429 / 3.575 |
|
||||||
@@ -398,6 +407,7 @@ It contains some useful key metrics about performance of your strategy on backte
|
|||||||
|-----------------------------+---------------------|
|
|-----------------------------+---------------------|
|
||||||
| Backtesting from | 2019-01-01 00:00:00 |
|
| Backtesting from | 2019-01-01 00:00:00 |
|
||||||
| Backtesting to | 2019-05-01 00:00:00 |
|
| Backtesting to | 2019-05-01 00:00:00 |
|
||||||
|
| Trading Mode | Spot |
|
||||||
| Max open trades | 3 |
|
| Max open trades | 3 |
|
||||||
| | |
|
| | |
|
||||||
| Total/Daily Avg Trades | 429 / 3.575 |
|
| Total/Daily Avg Trades | 429 / 3.575 |
|
||||||
@@ -452,6 +462,7 @@ It contains some useful key metrics about performance of your strategy on backte
|
|||||||
|
|
||||||
- `Backtesting from` / `Backtesting to`: Backtesting range (usually defined with the `--timerange` option).
|
- `Backtesting from` / `Backtesting to`: Backtesting range (usually defined with the `--timerange` option).
|
||||||
- `Max open trades`: Setting of `max_open_trades` (or `--max-open-trades`) - or number of pairs in the pairlist (whatever is lower).
|
- `Max open trades`: Setting of `max_open_trades` (or `--max-open-trades`) - or number of pairs in the pairlist (whatever is lower).
|
||||||
|
- `Trading Mode`: Spot or Futures trading.
|
||||||
- `Total/Daily Avg Trades`: Identical to the total trades of the backtest output table / Total trades divided by the backtesting duration in days (this will give you information about how many trades to expect from the strategy).
|
- `Total/Daily Avg Trades`: Identical to the total trades of the backtest output table / Total trades divided by the backtesting duration in days (this will give you information about how many trades to expect from the strategy).
|
||||||
- `Starting balance`: Start balance - as given by dry-run-wallet (config or command line).
|
- `Starting balance`: Start balance - as given by dry-run-wallet (config or command line).
|
||||||
- `Final balance`: Final balance - starting balance + absolute profit.
|
- `Final balance`: Final balance - starting balance + absolute profit.
|
||||||
@@ -530,10 +541,10 @@ You can then load the trades to perform further analysis as shown in the [data a
|
|||||||
Since backtesting lacks some detailed information about what happens within a candle, it needs to take a few assumptions:
|
Since backtesting lacks some detailed information about what happens within a candle, it needs to take a few assumptions:
|
||||||
|
|
||||||
- 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 unless a custom price logic has been specified
|
||||||
- All orders are filled at the requested price (no slippage) as long as the price is within the candle's high/low range
|
- 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
|
- Exits free their trade slot for a new trade with a different pair
|
||||||
- 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%)
|
||||||
@@ -555,6 +566,7 @@ Since backtesting lacks some detailed information about what happens within a ca
|
|||||||
- Stoploss
|
- Stoploss
|
||||||
- ROI
|
- ROI
|
||||||
- Trailing stoploss
|
- Trailing stoploss
|
||||||
|
- Position reversals (futures only) happen if an entry signal in the other direction than the closing trade triggers at the candle the existing trade closes.
|
||||||
|
|
||||||
Taking these assumptions, backtesting tries to mirror real trading as closely as possible. However, backtesting will **never** replace running a strategy in dry-run mode.
|
Taking these assumptions, backtesting tries to mirror real trading as closely as possible. However, backtesting will **never** replace running a strategy in dry-run mode.
|
||||||
Also, keep in mind that past results don't guarantee future success.
|
Also, keep in mind that past results don't guarantee future success.
|
||||||
@@ -569,7 +581,7 @@ These limits are usually listed in the exchange documentation as "trading rules"
|
|||||||
Backtesting (as well as live and dry-run) does honor these limits, and will ensure that a stoploss can be placed below this value - so the value will be slightly higher than what the exchange specifies.
|
Backtesting (as well as live and dry-run) does honor these limits, and will ensure that a stoploss can be placed below this value - so the value will be slightly higher than what the exchange specifies.
|
||||||
Freqtrade has however no information about historic limits.
|
Freqtrade has however no information about historic limits.
|
||||||
|
|
||||||
This can lead to situations where trading-limits are inflated by using a historic price, resulting in minimum amounts > 50$.
|
This can lead to situations where trading-limits are inflated by using a historic price, resulting in minimum amounts > 50\$.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
|
|||||||
+13
-4
@@ -12,41 +12,50 @@ This page explains the different parameters of the bot and how to run it.
|
|||||||
|
|
||||||
```
|
```
|
||||||
usage: freqtrade [-h] [-V]
|
usage: freqtrade [-h] [-V]
|
||||||
{trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver}
|
{trade,create-userdir,new-config,show-config,new-strategy,download-data,convert-data,convert-trade-data,trades-to-ohlcv,list-data,backtesting,backtesting-show,backtesting-analysis,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-markets,list-pairs,list-strategies,list-freqaimodels,list-timeframes,show-trades,test-pairlist,convert-db,install-ui,plot-dataframe,plot-profit,webserver,strategy-updater,lookahead-analysis,recursive-analysis}
|
||||||
...
|
...
|
||||||
|
|
||||||
Free, open source crypto trading bot
|
Free, open source crypto trading bot
|
||||||
|
|
||||||
positional arguments:
|
positional arguments:
|
||||||
{trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver}
|
{trade,create-userdir,new-config,show-config,new-strategy,download-data,convert-data,convert-trade-data,trades-to-ohlcv,list-data,backtesting,backtesting-show,backtesting-analysis,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-markets,list-pairs,list-strategies,list-freqaimodels,list-timeframes,show-trades,test-pairlist,convert-db,install-ui,plot-dataframe,plot-profit,webserver,strategy-updater,lookahead-analysis,recursive-analysis}
|
||||||
trade Trade module.
|
trade Trade module.
|
||||||
create-userdir Create user-data directory.
|
create-userdir Create user-data directory.
|
||||||
new-config Create new config
|
new-config Create new config
|
||||||
|
show-config Show resolved config
|
||||||
new-strategy Create new strategy
|
new-strategy Create new strategy
|
||||||
download-data Download backtesting data.
|
download-data Download backtesting data.
|
||||||
convert-data Convert candle (OHLCV) data from one format to
|
convert-data Convert candle (OHLCV) data from one format to
|
||||||
another.
|
another.
|
||||||
convert-trade-data Convert trade data from one format to another.
|
convert-trade-data Convert trade data from one format to another.
|
||||||
|
trades-to-ohlcv Convert trade data to OHLCV data.
|
||||||
list-data List downloaded data.
|
list-data List downloaded data.
|
||||||
backtesting Backtesting module.
|
backtesting Backtesting module.
|
||||||
|
backtesting-show Show past Backtest results
|
||||||
|
backtesting-analysis
|
||||||
|
Backtest Analysis module.
|
||||||
edge Edge module.
|
edge Edge module.
|
||||||
hyperopt Hyperopt module.
|
hyperopt Hyperopt module.
|
||||||
hyperopt-list List Hyperopt results
|
hyperopt-list List Hyperopt results
|
||||||
hyperopt-show Show details of Hyperopt results
|
hyperopt-show Show details of Hyperopt results
|
||||||
list-exchanges Print available exchanges.
|
list-exchanges Print available exchanges.
|
||||||
list-hyperopts Print available hyperopt classes.
|
|
||||||
list-markets Print markets on exchange.
|
list-markets Print markets on exchange.
|
||||||
list-pairs Print pairs on exchange.
|
list-pairs Print pairs on exchange.
|
||||||
list-strategies Print available strategies.
|
list-strategies Print available strategies.
|
||||||
|
list-freqaimodels Print available freqAI models.
|
||||||
list-timeframes Print available timeframes for the exchange.
|
list-timeframes Print available timeframes for the exchange.
|
||||||
show-trades Show trades.
|
show-trades Show trades.
|
||||||
test-pairlist Test your pairlist configuration.
|
test-pairlist Test your pairlist configuration.
|
||||||
|
convert-db Migrate database to different system
|
||||||
install-ui Install FreqUI
|
install-ui Install FreqUI
|
||||||
plot-dataframe Plot candles with indicators.
|
plot-dataframe Plot candles with indicators.
|
||||||
plot-profit Generate plot showing profits.
|
plot-profit Generate plot showing profits.
|
||||||
webserver Webserver module.
|
webserver Webserver module.
|
||||||
|
strategy-updater updates outdated strategy files to the current version
|
||||||
|
lookahead-analysis Check for potential look ahead bias.
|
||||||
|
recursive-analysis Check for potential recursive formula issue.
|
||||||
|
|
||||||
optional arguments:
|
options:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
-V, --version show program's version number and exit
|
-V, --version show program's version number and exit
|
||||||
|
|
||||||
|
|||||||
+57
-20
@@ -39,13 +39,19 @@ Please note that Environment variables will overwrite corresponding settings in
|
|||||||
|
|
||||||
Common example:
|
Common example:
|
||||||
|
|
||||||
```
|
``` bash
|
||||||
FREQTRADE__TELEGRAM__CHAT_ID=<telegramchatid>
|
FREQTRADE__TELEGRAM__CHAT_ID=<telegramchatid>
|
||||||
FREQTRADE__TELEGRAM__TOKEN=<telegramToken>
|
FREQTRADE__TELEGRAM__TOKEN=<telegramToken>
|
||||||
FREQTRADE__EXCHANGE__KEY=<yourExchangeKey>
|
FREQTRADE__EXCHANGE__KEY=<yourExchangeKey>
|
||||||
FREQTRADE__EXCHANGE__SECRET=<yourExchangeSecret>
|
FREQTRADE__EXCHANGE__SECRET=<yourExchangeSecret>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Json lists are parsed as json - so you can use the following to set a list of pairs:
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
export FREQTRADE__EXCHANGE__PAIR_WHITELIST='["BTC/USDT", "ETH/USDT"]'
|
||||||
|
```
|
||||||
|
|
||||||
!!! 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.
|
||||||
|
|
||||||
@@ -54,7 +60,7 @@ FREQTRADE__EXCHANGE__SECRET=<yourExchangeSecret>
|
|||||||
|
|
||||||
??? Warning "Loading sequence"
|
??? 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.
|
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.
|
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
|
||||||
|
|
||||||
@@ -123,6 +129,19 @@ This is similar to using multiple `--config` parameters, but simpler in usage as
|
|||||||
|
|
||||||
If multiple files are in the `add_config_files` section, then they will be assumed to be at identical levels, having the last occurrence override the earlier config (unless a parent already defined such a key).
|
If multiple files are in the `add_config_files` section, then they will be assumed to be at identical levels, having the last occurrence override the earlier config (unless a parent already defined such a key).
|
||||||
|
|
||||||
|
## Editor autocomplete and validation
|
||||||
|
|
||||||
|
If you are using an editor that supports JSON schema, you can use the schema provided by Freqtrade to get autocompletion and validation of your configuration file by adding the following line to the top of your configuration file:
|
||||||
|
|
||||||
|
``` json
|
||||||
|
{
|
||||||
|
"$schema": "https://schema.freqtrade.io/schema.json",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
??? Note "Develop version"
|
||||||
|
The develop schema is available as `https://schema.freqtrade.io/schema_dev.json` - though we recommend to stick to the stable version for the best experience.
|
||||||
|
|
||||||
## Configuration parameters
|
## Configuration parameters
|
||||||
|
|
||||||
The table below will list all configuration parameters available.
|
The table below will list all configuration parameters available.
|
||||||
@@ -133,10 +152,10 @@ Freqtrade can also load many options via command line (CLI) arguments (check out
|
|||||||
|
|
||||||
The prevalence for all Options is as follows:
|
The prevalence for all Options is as follows:
|
||||||
|
|
||||||
- CLI arguments override any other option
|
* CLI arguments override any other option
|
||||||
- [Environment Variables](#environment-variables)
|
* [Environment Variables](#environment-variables)
|
||||||
- Configuration files are used in sequence (the last file wins) and override Strategy configurations.
|
* Configuration files are used in sequence (the last file wins) and override Strategy configurations.
|
||||||
- Strategy configurations are only used if they are not set via configuration or command-line arguments. These options are marked with [Strategy Override](#parameters-in-the-strategy) in the below table.
|
* Strategy configurations are only used if they are not set via configuration or command-line arguments. These options are marked with [Strategy Override](#parameters-in-the-strategy) in the below table.
|
||||||
|
|
||||||
### Parameters table
|
### Parameters table
|
||||||
|
|
||||||
@@ -155,7 +174,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
|||||||
| `timeframe` | The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). Usually missing in configuration, and specified in the strategy. [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** String
|
| `timeframe` | The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). Usually missing in configuration, and specified in the strategy. [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** String
|
||||||
| `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency). <br> **Datatype:** String
|
| `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency). <br> **Datatype:** String
|
||||||
| `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode. <br>*Defaults to `true`.* <br> **Datatype:** Boolean
|
| `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode. <br>*Defaults to `true`.* <br> **Datatype:** Boolean
|
||||||
| `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in Dry Run mode.<br>*Defaults to `1000`.* <br> **Datatype:** Float
|
| `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in Dry Run mode. [More information below](#dry-run-wallet)<br>*Defaults to `1000`.* <br> **Datatype:** Float or Dict
|
||||||
| `cancel_open_orders_on_exit` | Cancel open orders when the `/stop` RPC command is issued, `Ctrl+C` is pressed or the bot dies unexpectedly. When set to `true`, this allows you to use `/stop` to cancel unfilled and partially filled orders in the event of a market crash. It does not impact open positions. <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
| `cancel_open_orders_on_exit` | Cancel open orders when the `/stop` RPC command is issued, `Ctrl+C` is pressed or the bot dies unexpectedly. When set to `true`, this allows you to use `/stop` to cancel unfilled and partially filled orders in the event of a market crash. It does not impact open positions. <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||||
| `process_only_new_candles` | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `true`.* <br> **Datatype:** Boolean
|
| `process_only_new_candles` | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `true`.* <br> **Datatype:** Boolean
|
||||||
| `minimal_roi` | **Required.** Set the threshold as ratio the bot will use to exit a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Dict
|
| `minimal_roi` | **Required.** Set the threshold as ratio the bot will use to exit a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Dict
|
||||||
@@ -170,7 +189,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
|||||||
| `margin_mode` | When trading with leverage, this determines if the collateral owned by the trader will be shared or isolated to each trading pair [leverage documentation](leverage.md). <br> **Datatype:** String
|
| `margin_mode` | When trading with leverage, this determines if the collateral owned by the trader will be shared or isolated to each trading pair [leverage documentation](leverage.md). <br> **Datatype:** String
|
||||||
| `liquidation_buffer` | A ratio specifying how large of a safety net to place between the liquidation price and the stoploss to prevent a position from reaching the liquidation price [leverage documentation](leverage.md). <br>*Defaults to `0.05`.* <br> **Datatype:** Float
|
| `liquidation_buffer` | A ratio specifying how large of a safety net to place between the liquidation price and the stoploss to prevent a position from reaching the liquidation price [leverage documentation](leverage.md). <br>*Defaults to `0.05`.* <br> **Datatype:** Float
|
||||||
| | **Unfilled timeout**
|
| | **Unfilled timeout**
|
||||||
| `unfilledtimeout.entry` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled entry order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
|
| `unfilledtimeout.entry` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled entry order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
|
||||||
| `unfilledtimeout.exit` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled exit order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
|
| `unfilledtimeout.exit` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled exit order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
|
||||||
| `unfilledtimeout.unit` | Unit to use in unfilledtimeout setting. Note: If you set unfilledtimeout.unit to "seconds", "internals.process_throttle_secs" must be inferior or equal to timeout [Strategy Override](#parameters-in-the-strategy). <br> *Defaults to `"minutes"`.* <br> **Datatype:** String
|
| `unfilledtimeout.unit` | Unit to use in unfilledtimeout setting. Note: If you set unfilledtimeout.unit to "seconds", "internals.process_throttle_secs" must be inferior or equal to timeout [Strategy Override](#parameters-in-the-strategy). <br> *Defaults to `"minutes"`.* <br> **Datatype:** String
|
||||||
| `unfilledtimeout.exit_timeout_count` | How many times can exit orders time out. Once this number of timeouts is reached, an emergency exit is triggered. 0 to disable and allow unlimited order cancels. [Strategy Override](#parameters-in-the-strategy).<br>*Defaults to `0`.* <br> **Datatype:** Integer
|
| `unfilledtimeout.exit_timeout_count` | How many times can exit orders time out. Once this number of timeouts is reached, an emergency exit is triggered. 0 to disable and allow unlimited order cancels. [Strategy Override](#parameters-in-the-strategy).<br>*Defaults to `0`.* <br> **Datatype:** Integer
|
||||||
@@ -209,15 +228,14 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
|||||||
| `exchange.ccxt_async_config` | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://docs.ccxt.com/#/README?id=overriding-exchange-properties-upon-instantiation) <br> **Datatype:** Dict
|
| `exchange.ccxt_async_config` | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://docs.ccxt.com/#/README?id=overriding-exchange-properties-upon-instantiation) <br> **Datatype:** Dict
|
||||||
| `exchange.enable_ws` | Enable the usage of Websockets for the exchange. <br>[More information](#consuming-exchange-websockets).<br>*Defaults to `true`.* <br> **Datatype:** Boolean
|
| `exchange.enable_ws` | Enable the usage of Websockets for the exchange. <br>[More information](#consuming-exchange-websockets).<br>*Defaults to `true`.* <br> **Datatype:** Boolean
|
||||||
| `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded. <br>*Defaults to `60` minutes.* <br> **Datatype:** Positive Integer
|
| `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded. <br>*Defaults to `60` minutes.* <br> **Datatype:** Positive Integer
|
||||||
| `exchange.skip_pair_validation` | Skip pairlist validation on startup.<br>*Defaults to `false`*<br> **Datatype:** Boolean
|
|
||||||
| `exchange.skip_open_order_update` | Skips open order updates on startup should the exchange cause problems. Only relevant in live conditions.<br>*Defaults to `false`*<br> **Datatype:** Boolean
|
| `exchange.skip_open_order_update` | Skips open order updates on startup should the exchange cause problems. Only relevant in live conditions.<br>*Defaults to `false`*<br> **Datatype:** Boolean
|
||||||
| `exchange.unknown_fee_rate` | Fallback value to use when calculating trading fees. This can be useful for exchanges which have fees in non-tradable currencies. The value provided here will be multiplied with the "fee cost".<br>*Defaults to `None`<br> **Datatype:** float
|
| `exchange.unknown_fee_rate` | Fallback value to use when calculating trading fees. This can be useful for exchanges which have fees in non-tradable currencies. The value provided here will be multiplied with the "fee cost".<br>*Defaults to `None`<br> **Datatype:** float
|
||||||
| `exchange.log_responses` | Log relevant exchange responses. For debug mode only - use with care.<br>*Defaults to `false`*<br> **Datatype:** Boolean
|
| `exchange.log_responses` | Log relevant exchange responses. For debug mode only - use with care.<br>*Defaults to `false`*<br> **Datatype:** Boolean
|
||||||
|
| `exchange.only_from_ccxt` | Prevent data-download from data.binance.vision. Leaving this as false can greatly speed up downloads, but may be problematic if the site is not available.<br>*Defaults to `false`*<br> **Datatype:** Boolean
|
||||||
| `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now. <br>*Defaults to `true`.* <br> **Datatype:** Boolean
|
| `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now. <br>*Defaults to `true`.* <br> **Datatype:** Boolean
|
||||||
| | **Plugins**
|
| | **Plugins**
|
||||||
| `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation of all possible configuration options.
|
| `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation of all possible configuration options.
|
||||||
| `pairlists` | Define one or more pairlists to be used. [More information](plugins.md#pairlists-and-pairlist-handlers). <br>*Defaults to `StaticPairList`.* <br> **Datatype:** List of Dicts
|
| `pairlists` | Define one or more pairlists to be used. [More information](plugins.md#pairlists-and-pairlist-handlers). <br>*Defaults to `StaticPairList`.* <br> **Datatype:** List of Dicts
|
||||||
| `protections` | Define one or more protections to be used. [More information](plugins.md#protections). <br> **Datatype:** List of Dicts
|
|
||||||
| | **Telegram**
|
| | **Telegram**
|
||||||
| `telegram.enabled` | Enable the usage of Telegram. <br> **Datatype:** Boolean
|
| `telegram.enabled` | Enable the usage of Telegram. <br> **Datatype:** Boolean
|
||||||
| `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
| `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
||||||
@@ -284,10 +302,10 @@ Values set in the configuration file always overwrite values set in the strategy
|
|||||||
* `order_time_in_force`
|
* `order_time_in_force`
|
||||||
* `unfilledtimeout`
|
* `unfilledtimeout`
|
||||||
* `disable_dataframe_checks`
|
* `disable_dataframe_checks`
|
||||||
- `use_exit_signal`
|
* `use_exit_signal`
|
||||||
* `exit_profit_only`
|
* `exit_profit_only`
|
||||||
- `exit_profit_offset`
|
* `exit_profit_offset`
|
||||||
- `ignore_roi_if_entry_signal`
|
* `ignore_roi_if_entry_signal`
|
||||||
* `ignore_buying_expired_candle_after`
|
* `ignore_buying_expired_candle_after`
|
||||||
* `position_adjustment_enable`
|
* `position_adjustment_enable`
|
||||||
* `max_entry_position_adjustment`
|
* `max_entry_position_adjustment`
|
||||||
@@ -300,18 +318,37 @@ There are several methods to configure how much of the stake currency the bot wi
|
|||||||
|
|
||||||
The minimum stake amount will depend on exchange and pair and is usually listed in the exchange support pages.
|
The minimum stake amount will depend on exchange and pair and is usually listed in the exchange support pages.
|
||||||
|
|
||||||
Assuming the minimum tradable amount for XRP/USD is 20 XRP (given by the exchange), and the price is 0.6$, the minimum stake amount to buy this pair is `20 * 0.6 ~= 12`.
|
Assuming the minimum tradable amount for XRP/USD is 20 XRP (given by the exchange), and the price is 0.6\$, the minimum stake amount to buy this pair is `20 * 0.6 ~= 12`.
|
||||||
This exchange has also a limit on USD - where all orders must be > 10$ - which however does not apply in this case.
|
This exchange has also a limit on USD - where all orders must be > 10\$ - which however does not apply in this case.
|
||||||
|
|
||||||
To guarantee safe execution, freqtrade will not allow buying with a stake-amount of 10.1$, instead, it'll make sure that there's enough space to place a stoploss below the pair (+ an offset, defined by `amount_reserve_percent`, which defaults to 5%).
|
To guarantee safe execution, freqtrade will not allow buying with a stake-amount of 10.1\$, instead, it'll make sure that there's enough space to place a stoploss below the pair (+ an offset, defined by `amount_reserve_percent`, which defaults to 5%).
|
||||||
|
|
||||||
With a reserve of 5%, the minimum stake amount would be ~12.6$ (`12 * (1 + 0.05)`). If we take into account a stoploss of 10% on top of that - we'd end up with a value of ~14$ (`12.6 / (1 - 0.1)`).
|
With a reserve of 5%, the minimum stake amount would be ~12.6\$ (`12 * (1 + 0.05)`). If we take into account a stoploss of 10% on top of that - we'd end up with a value of ~14\$ (`12.6 / (1 - 0.1)`).
|
||||||
|
|
||||||
To limit this calculation in case of large stoploss values, the calculated minimum stake-limit will never be more than 50% above the real limit.
|
To limit this calculation in case of large stoploss values, the calculated minimum stake-limit will never be more than 50% above the real limit.
|
||||||
|
|
||||||
!!! Warning
|
!!! Warning
|
||||||
Since the limits on exchanges are usually stable and are not updated often, some pairs can show pretty high minimum limits, simply because the price increased a lot since the last limit adjustment by the exchange. Freqtrade adjusts the stake-amount to this value, unless it's > 30% more than the calculated/desired stake-amount - in which case the trade is rejected.
|
Since the limits on exchanges are usually stable and are not updated often, some pairs can show pretty high minimum limits, simply because the price increased a lot since the last limit adjustment by the exchange. Freqtrade adjusts the stake-amount to this value, unless it's > 30% more than the calculated/desired stake-amount - in which case the trade is rejected.
|
||||||
|
|
||||||
|
#### Dry-run wallet
|
||||||
|
|
||||||
|
When running in dry-run mode, the bot will use a simulated wallet to execute trades. The starting balance of this wallet is defined by `dry_run_wallet` (defaults to 1000).
|
||||||
|
For more complex scenarios, you can also assign a dictionary to `dry_run_wallet` to define the starting balance for each currency.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"dry_run_wallet": {
|
||||||
|
"BTC": 0.01,
|
||||||
|
"ETH": 2,
|
||||||
|
"USDT": 1000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Command line options (`--dry-run-wallet`) can be used to override the configuration value, but only for the float value, not for the dictionary. If you'd like to use the dictionary, please adjust the configuration file.
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
Balances not in stake-currency will not be used for trading, but are shown as part of the wallet balance.
|
||||||
|
On Cross-margin exchanges, the wallet balance may be used to calculate the available collateral for trading.
|
||||||
|
|
||||||
#### Tradable balance
|
#### Tradable balance
|
||||||
|
|
||||||
By default, the bot assumes that the `complete amount - 1%` is at it's disposal, and when using [dynamic stake amount](#dynamic-stake-amount), it will split the complete balance into `max_open_trades` buckets per trade.
|
By default, the bot assumes that the `complete amount - 1%` is at it's disposal, and when using [dynamic stake amount](#dynamic-stake-amount), it will split the complete balance into `max_open_trades` buckets per trade.
|
||||||
@@ -351,9 +388,9 @@ To overcome this, the option `amend_last_stake_amount` can be set to `True`, whi
|
|||||||
|
|
||||||
In the example above this would mean:
|
In the example above this would mean:
|
||||||
|
|
||||||
- Trade1: 400 USDT
|
* Trade1: 400 USDT
|
||||||
- Trade2: 400 USDT
|
* Trade2: 400 USDT
|
||||||
- Trade3: 200 USDT
|
* Trade3: 200 USDT
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
This option only applies with [Static stake amount](#static-stake-amount) - since [Dynamic stake amount](#dynamic-stake-amount) divides the balances evenly.
|
This option only applies with [Static stake amount](#static-stake-amount) - since [Dynamic stake amount](#dynamic-stake-amount) divides the balances evenly.
|
||||||
|
|||||||
+36
-15
@@ -11,9 +11,8 @@ Without provided configuration, `--exchange` becomes mandatory.
|
|||||||
You can use a relative timerange (`--days 20`) or an absolute starting point (`--timerange 20200101-`). For incremental downloads, the relative approach should be used.
|
You can use a relative timerange (`--days 20`) or an absolute starting point (`--timerange 20200101-`). For incremental downloads, the relative approach should be used.
|
||||||
|
|
||||||
!!! Tip "Tip: Updating existing data"
|
!!! Tip "Tip: Updating existing data"
|
||||||
If you already have backtesting data available in your data-directory and would like to refresh this data up to today, freqtrade will automatically calculate the data missing for the existing pairs and the download will occur from the latest available point until "now", neither --days or --timerange parameters are required. Freqtrade will keep the available data and only download the missing data.
|
If you already have backtesting data available in your data-directory and would like to refresh this data up to today, freqtrade will automatically calculate the missing timerange for the existing pairs and the download will occur from the latest available point until "now", neither `--days` or `--timerange` parameters are required. Freqtrade will keep the available data and only download the missing data.
|
||||||
If you are updating existing data after inserting new pairs that you have no data for, use `--new-pairs-days xx` parameter. Specified number of days will be downloaded for new pairs while old pairs will be updated with missing data only.
|
If you are updating existing data after inserting new pairs that you have no data for, use the `--new-pairs-days xx` parameter. Specified number of days will be downloaded for new pairs while old pairs will be updated with missing data only.
|
||||||
If you use `--days xx` parameter alone - data for specified number of days will be downloaded for _all_ pairs. Be careful, if specified number of days is smaller than gap between now and last downloaded candle - freqtrade will delete all existing data to avoid gaps in candle data.
|
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
@@ -90,7 +89,7 @@ Common arguments:
|
|||||||
|
|
||||||
!!! Tip "Downloading all data for one quote currency"
|
!!! Tip "Downloading all data for one quote currency"
|
||||||
Often, you'll want to download data for all pairs of a specific quote-currency. In such cases, you can use the following shorthand:
|
Often, you'll want to download data for all pairs of a specific quote-currency. In such cases, you can use the following shorthand:
|
||||||
`freqtrade download-data --exchange binance --pairs .*/USDT <...>`. The provided "pairs" string will be expanded to contain all active pairs on the exchange.
|
`freqtrade download-data --exchange binance --pairs ".*/USDT" <...>`. The provided "pairs" string will be expanded to contain all active pairs on the exchange.
|
||||||
To also download data for inactive (delisted) pairs, add `--include-inactive-pairs` to the command.
|
To also download data for inactive (delisted) pairs, add `--include-inactive-pairs` to the command.
|
||||||
|
|
||||||
!!! Note "Startup period"
|
!!! Note "Startup period"
|
||||||
@@ -117,16 +116,17 @@ freqtrade download-data --exchange binance --pairs ETH/USDT XRP/USDT BTC/USDT
|
|||||||
or as regex (in this case, to download all active USDT pairs)
|
or as regex (in this case, to download all active USDT pairs)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
freqtrade download-data --exchange binance --pairs .*/USDT
|
freqtrade download-data --exchange binance --pairs ".*/USDT"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Other Notes
|
### Other Notes
|
||||||
|
|
||||||
* To use a different directory than the exchange specific default, use `--datadir user_data/data/some_directory`.
|
* To use a different directory than the exchange specific default, use `--datadir user_data/data/some_directory`.
|
||||||
* To change the exchange used to download the historical data from, please use a different configuration file (you'll probably need to adjust rate limits etc.)
|
* To change the exchange used to download the historical data from, either use `--exchange <exchange>` - or specify a different configuration file.
|
||||||
* To use `pairs.json` from some other directory, use `--pairs-file some_other_dir/pairs.json`.
|
* To use `pairs.json` from some other directory, use `--pairs-file some_other_dir/pairs.json`.
|
||||||
* To download historical candle (OHLCV) data for only 10 days, use `--days 10` (defaults to 30 days).
|
* To download historical candle (OHLCV) data for only 10 days, use `--days 10` (defaults to 30 days).
|
||||||
* To download historical candle (OHLCV) data from a fixed starting point, use `--timerange 20200101-` - which will download all data from January 1st, 2020.
|
* To download historical candle (OHLCV) data from a fixed starting point, use `--timerange 20200101-` - which will download all data from January 1st, 2020.
|
||||||
|
* Given starting points are ignored if data is already available, downloading only missing data up to today.
|
||||||
* Use `--timeframes` to specify what timeframe download the historical candle (OHLCV) data for. Default is `--timeframes 1m 5m` which will download 1-minute and 5-minute data.
|
* Use `--timeframes` to specify what timeframe download the historical candle (OHLCV) data for. Default is `--timeframes 1m 5m` which will download 1-minute and 5-minute data.
|
||||||
* To use exchange, timeframe and list of pairs as defined in your configuration file, use the `-c/--config` option. With this, the script uses the whitelist defined in the config as the list of currency pairs to download data for and does not require the pairs.json file. You can combine `-c/--config` with most other options.
|
* To use exchange, timeframe and list of pairs as defined in your configuration file, use the `-c/--config` option. With this, the script uses the whitelist defined in the config as the list of currency pairs to download data for and does not require the pairs.json file. You can combine `-c/--config` with most other options.
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ Freqtrade currently supports the following data-formats:
|
|||||||
* `feather` - a dataformat based on Apache Arrow
|
* `feather` - a dataformat based on Apache Arrow
|
||||||
* `json` - plain "text" json files
|
* `json` - plain "text" json files
|
||||||
* `jsongz` - a gzip-zipped version of json files
|
* `jsongz` - a gzip-zipped version of json files
|
||||||
* `hdf5` - a high performance datastore
|
* `hdf5` - a high performance datastore (deprecated)
|
||||||
* `parquet` - columnar datastore (OHLCV only)
|
* `parquet` - columnar datastore (OHLCV only)
|
||||||
|
|
||||||
By default, both OHLCV data and trades data are stored in the `feather` format.
|
By default, both OHLCV data and trades data are stored in the `feather` format.
|
||||||
@@ -423,7 +423,8 @@ You can get a list of downloaded data using the `list-data` sub-command.
|
|||||||
usage: freqtrade list-data [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
|
usage: freqtrade list-data [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
|
||||||
[--userdir PATH] [--exchange EXCHANGE]
|
[--userdir PATH] [--exchange EXCHANGE]
|
||||||
[--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}]
|
[--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}]
|
||||||
[-p PAIRS [PAIRS ...]]
|
[--data-format-trades {json,jsongz,hdf5,feather,parquet}]
|
||||||
|
[--trades] [-p PAIRS [PAIRS ...]]
|
||||||
[--trading-mode {spot,margin,futures}]
|
[--trading-mode {spot,margin,futures}]
|
||||||
[--show-timerange]
|
[--show-timerange]
|
||||||
|
|
||||||
@@ -433,6 +434,10 @@ options:
|
|||||||
--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}
|
--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}
|
||||||
Storage format for downloaded candle (OHLCV) data.
|
Storage format for downloaded candle (OHLCV) data.
|
||||||
(default: `feather`).
|
(default: `feather`).
|
||||||
|
--data-format-trades {json,jsongz,hdf5,feather,parquet}
|
||||||
|
Storage format for downloaded trades data. (default:
|
||||||
|
`feather`).
|
||||||
|
--trades Work on trades data instead of OHLCV data.
|
||||||
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
|
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
|
||||||
Limit command to these pairs. Pairs are space-
|
Limit command to these pairs. Pairs are space-
|
||||||
separated.
|
separated.
|
||||||
@@ -465,13 +470,29 @@ Common arguments:
|
|||||||
```bash
|
```bash
|
||||||
> freqtrade list-data --userdir ~/.freqtrade/user_data/
|
> freqtrade list-data --userdir ~/.freqtrade/user_data/
|
||||||
|
|
||||||
Found 33 pair / timeframe combinations.
|
Found 33 pair / timeframe combinations.
|
||||||
pairs timeframe
|
┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━┓
|
||||||
---------- -----------------------------------------
|
┃ Pair ┃ Timeframe ┃ Type ┃
|
||||||
ADA/BTC 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d
|
┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━┩
|
||||||
ADA/ETH 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d
|
│ ADA/BTC │ 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d │ spot │
|
||||||
ETH/BTC 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d
|
│ ADA/ETH │ 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d │ spot │
|
||||||
ETH/USDT 5m, 15m, 30m, 1h, 2h, 4h
|
│ ETH/BTC │ 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d │ spot │
|
||||||
|
│ ETH/USDT │ 5m, 15m, 30m, 1h, 2h, 4h │ spot │
|
||||||
|
└───────────────┴───────────────────────────────────────────┴──────┘
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Show all trades data including from/to timerange
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
> freqtrade list-data --show --trades
|
||||||
|
Found trades data for 1 pair.
|
||||||
|
┏━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┓
|
||||||
|
┃ Pair ┃ Type ┃ From ┃ To ┃ Trades ┃
|
||||||
|
┡━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━┩
|
||||||
|
│ XRP/ETH │ spot │ 2019-10-11 00:00:11 │ 2019-10-13 11:19:28 │ 12477 │
|
||||||
|
└─────────┴──────┴─────────────────────┴─────────────────────┴────────┘
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Trades (tick) data
|
## Trades (tick) data
|
||||||
|
|||||||
+10
-1
@@ -75,7 +75,16 @@ Webhook terminology changed from "sell" to "exit", and from "buy" to "entry", re
|
|||||||
* `webhooksellfill`, `webhookexitfill` -> `exit_fill`
|
* `webhooksellfill`, `webhookexitfill` -> `exit_fill`
|
||||||
* `webhooksellcancel`, `webhookexitcancel` -> `exit_cancel`
|
* `webhooksellcancel`, `webhookexitcancel` -> `exit_cancel`
|
||||||
|
|
||||||
|
|
||||||
## Removal of `populate_any_indicators`
|
## Removal of `populate_any_indicators`
|
||||||
|
|
||||||
version 2023.3 saw the removal of `populate_any_indicators` in favor of split methods for feature engineering and targets. Please read the [migration document](strategy_migration.md#freqai-strategy) for full details.
|
version 2023.3 saw the removal of `populate_any_indicators` in favor of split methods for feature engineering and targets. Please read the [migration document](strategy_migration.md#freqai-strategy) for full details.
|
||||||
|
|
||||||
|
## Removal of `protections` from configuration
|
||||||
|
|
||||||
|
Setting protections from the configuration via `"protections": [],` has been removed in 2024.10, after having raised deprecation warnings for over 3 years.
|
||||||
|
|
||||||
|
## hdf5 data storage
|
||||||
|
|
||||||
|
Using hdf5 as data storage has been deprecated in 2024.12 and will be removed in 2025.1. We recommend switching to the feather data format.
|
||||||
|
|
||||||
|
Please use the [`convert-data` subcommand](data-download.md#sub-command-convert-data) to convert your existing data to one of the supported formats.
|
||||||
|
|||||||
+20
-18
@@ -116,7 +116,7 @@ A similar setup can also be taken for Pycharm - using `freqtrade` as module name
|
|||||||

|

|
||||||
|
|
||||||
!!! Note "Startup directory"
|
!!! Note "Startup directory"
|
||||||
This assumes that you have the repository checked out, and the editor is started at the repository root level (so setup.py is at the top level of your repository).
|
This assumes that you have the repository checked out, and the editor is started at the repository root level (so pyproject.toml is at the top level of your repository).
|
||||||
|
|
||||||
## ErrorHandling
|
## ErrorHandling
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ Hopefully you also want to contribute this back upstream.
|
|||||||
|
|
||||||
Whatever your motivations are - This should get you off the ground in trying to develop a new Pairlist Handler.
|
Whatever your motivations are - This should get you off the ground in trying to develop a new Pairlist Handler.
|
||||||
|
|
||||||
First of all, have a look at the [VolumePairList](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/pairlist/VolumePairList.py) Handler, and best copy this file with a name of your new Pairlist Handler.
|
First of all, have a look at the [VolumePairList](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/plugins/pairlist/VolumePairList.py) Handler, and best copy this file with a name of your new Pairlist Handler.
|
||||||
|
|
||||||
This is a simple Handler, which however serves as a good example on how to start developing.
|
This is a simple Handler, which however serves as a good example on how to start developing.
|
||||||
|
|
||||||
@@ -205,7 +205,7 @@ This is called with each iteration of the bot (only if the Pairlist Handler is a
|
|||||||
|
|
||||||
It must return the resulting pairlist (which may then be passed into the chain of Pairlist Handlers).
|
It must return the resulting pairlist (which may then be passed into the chain of Pairlist Handlers).
|
||||||
|
|
||||||
Validations are optional, the parent class exposes a `_verify_blacklist(pairlist)` and `_whitelist_for_active_markets(pairlist)` to do default filtering. Use this if you limit your result to a certain number of pairs - so the end-result is not shorter than expected.
|
Validations are optional, the parent class exposes a `verify_blacklist(pairlist)` and `_whitelist_for_active_markets(pairlist)` to do default filtering. Use this if you limit your result to a certain number of pairs - so the end-result is not shorter than expected.
|
||||||
|
|
||||||
#### filter_pairlist
|
#### filter_pairlist
|
||||||
|
|
||||||
@@ -219,14 +219,14 @@ The default implementation in the base class simply calls the `_validate_pair()`
|
|||||||
|
|
||||||
If overridden, it must return the resulting pairlist (which may then be passed into the next Pairlist Handler in the chain).
|
If overridden, it must return the resulting pairlist (which may then be passed into the next Pairlist Handler in the chain).
|
||||||
|
|
||||||
Validations are optional, the parent class exposes a `_verify_blacklist(pairlist)` and `_whitelist_for_active_markets(pairlist)` to do default filters. Use this if you limit your result to a certain number of pairs - so the end result is not shorter than expected.
|
Validations are optional, the parent class exposes a `verify_blacklist(pairlist)` and `_whitelist_for_active_markets(pairlist)` to do default filters. Use this if you limit your result to a certain number of pairs - so the end result is not shorter than expected.
|
||||||
|
|
||||||
In `VolumePairList`, this implements different methods of sorting, does early validation so only the expected number of pairs is returned.
|
In `VolumePairList`, this implements different methods of sorting, does early validation so only the expected number of pairs is returned.
|
||||||
|
|
||||||
##### sample
|
##### sample
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
|
def filter_pairlist(self, pairlist: list[str], tickers: dict) -> List[str]:
|
||||||
# Generate dynamic whitelist
|
# Generate dynamic whitelist
|
||||||
pairs = self._calculate_pairlist(pairlist, tickers)
|
pairs = self._calculate_pairlist(pairlist, tickers)
|
||||||
return pairs
|
return pairs
|
||||||
@@ -241,7 +241,6 @@ No protection should use datetime directly, but use the provided `date_now` vari
|
|||||||
|
|
||||||
!!! Tip "Writing a new Protection"
|
!!! Tip "Writing a new Protection"
|
||||||
Best copy one of the existing Protections to have a good example.
|
Best copy one of the existing Protections to have a good example.
|
||||||
Don't forget to register your protection in `constants.py` under the variable `AVAILABLE_PROTECTIONS` - otherwise it will not be selectable.
|
|
||||||
|
|
||||||
#### Implementation of a new protection
|
#### Implementation of a new protection
|
||||||
|
|
||||||
@@ -481,21 +480,24 @@ Once the PR against stable is merged (best right after merging):
|
|||||||
|
|
||||||
### pypi
|
### pypi
|
||||||
|
|
||||||
!!! Note
|
!!! Warning "Manual Releases"
|
||||||
This process is now automated as part of Github Actions.
|
This process is automated as part of Github Actions.
|
||||||
|
Manual pypi pushes should not be necessary.
|
||||||
|
|
||||||
To create a pypi release, please run the following commands:
|
??? example "Manual release"
|
||||||
|
To manually create a pypi release, please run the following commands:
|
||||||
|
|
||||||
Additional requirement: `wheel`, `twine` (for uploading), account on pypi with proper permissions.
|
Additional requirement: `wheel`, `twine` (for uploading), account on pypi with proper permissions.
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
python setup.py sdist bdist_wheel
|
pip install -U build
|
||||||
|
python -m build --sdist --wheel
|
||||||
|
|
||||||
# For pypi test (to check if some change to the installation did work)
|
# For pypi test (to check if some change to the installation did work)
|
||||||
twine upload --repository-url https://test.pypi.org/legacy/ dist/*
|
twine upload --repository-url https://test.pypi.org/legacy/ dist/*
|
||||||
|
|
||||||
# For production:
|
# For production:
|
||||||
twine upload dist/*
|
twine upload dist/*
|
||||||
```
|
```
|
||||||
|
|
||||||
Please don't push non-releases to the productive / real pypi instance.
|
Please don't push non-releases to the productive / real pypi instance.
|
||||||
|
|||||||
+59
-6
@@ -217,12 +217,12 @@ 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.
|
||||||
|
|
||||||
## HTX (formerly Huobi)
|
## HTX
|
||||||
|
|
||||||
!!! Tip "Stoploss on Exchange"
|
!!! Tip "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.
|
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
|
||||||
|
|
||||||
OKX requires a passphrase for each api key, you will therefore need to add this key into the configuration so your exchange section looks as follows:
|
OKX requires a passphrase for each api key, you will therefore need to add this key into the configuration so your exchange section looks as follows:
|
||||||
|
|
||||||
@@ -236,6 +236,9 @@ OKX requires a passphrase for each api key, you will therefore need to add this
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you've registered with OKX on the host my.okx.com (OKX EAA)- you will need to use `"myokx"` as the exchange name.
|
||||||
|
Using the wrong exchange will result in the error "OKX Error 50119: API key doesn't exist" - as the 2 are separate entities.
|
||||||
|
|
||||||
!!! Warning
|
!!! Warning
|
||||||
OKX only provides 100 candles per api call. Therefore, the strategy will only have a pretty low amount of data available in backtesting mode.
|
OKX only provides 100 candles per api call. Therefore, the strategy will only have a pretty low amount of data available in backtesting mode.
|
||||||
|
|
||||||
@@ -252,21 +255,36 @@ OKX requires a passphrase for each api key, you will therefore need to add this
|
|||||||
Gate.io allows the use of `POINT` to pay for fees. As this is not a tradable currency (no regular market available), automatic fee calculations will fail (and default to a fee of 0).
|
Gate.io allows the use of `POINT` to pay for fees. As this is not a tradable currency (no regular market available), automatic fee calculations will fail (and default to a fee of 0).
|
||||||
The configuration parameter `exchange.unknown_fee_rate` can be used to specify the exchange rate between Point and the stake currency. Obviously, changing the stake-currency will also require changes to this value.
|
The configuration parameter `exchange.unknown_fee_rate` can be used to specify the exchange rate between Point and the stake currency. Obviously, changing the stake-currency will also require changes to this value.
|
||||||
|
|
||||||
|
Gate API keys require the following permissions on top of the market type you want to trade:
|
||||||
|
|
||||||
|
* "Spot Trade" _or_ "Perpetual Futures" (Read and Write) (either select both, or the one matching the market you want to trade)
|
||||||
|
* "Wallet" (read only)
|
||||||
|
* "Account" (read only)
|
||||||
|
|
||||||
|
Without these permissions, the bot will not start correctly and show errors like "permission missing".
|
||||||
|
|
||||||
## Bybit
|
## Bybit
|
||||||
|
|
||||||
Futures trading on bybit is currently supported for USDT markets, and will use isolated futures mode.
|
Futures trading on bybit is currently supported for USDT markets, and will use isolated futures mode.
|
||||||
Users with unified accounts (there's no way back) can create a Sub-account which will start as "non-unified", and can therefore use isolated futures.
|
|
||||||
On startup, freqtrade will set the position mode to "One-way Mode" for the whole (sub)account. This avoids making this call over and over again (slowing down bot operations), but means that changes to this setting may result in exceptions and errors
|
On startup, freqtrade will set the position mode to "One-way Mode" for the whole (sub)account. This avoids making this call over and over again (slowing down bot operations), but means that changes to this setting may result in exceptions and errors.
|
||||||
|
|
||||||
As bybit doesn't provide funding rate history, the dry-run calculation is used for live trades as well.
|
As bybit doesn't provide funding rate history, the dry-run calculation is used for live trades as well.
|
||||||
|
|
||||||
API Keys for live futures trading (Subaccount on non-unified) must have the following permissions:
|
API Keys for live futures trading must have the following permissions:
|
||||||
|
|
||||||
* Read-write
|
* Read-write
|
||||||
* Contract - Orders
|
* Contract - Orders
|
||||||
* Contract - Positions
|
* Contract - Positions
|
||||||
|
|
||||||
We do strongly recommend to limit all API keys to the IP you're going to use it from.
|
We do strongly recommend to limit all API keys to the IP you're going to use it from.
|
||||||
|
|
||||||
|
!!! Warning "Unified accounts"
|
||||||
|
Freqtrade assumes accounts to be dedicated to the bot.
|
||||||
|
We therefore recommend the usage of one subaccount per bot. This is especially important when using unified accounts.
|
||||||
|
Other configurations (multiple bots on one account, manual non-bot trades on the bot account) are not supported and may lead to unexpected behavior.
|
||||||
|
|
||||||
|
|
||||||
!!! Tip "Stoploss on Exchange"
|
!!! Tip "Stoploss on Exchange"
|
||||||
Bybit (futures only) supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange.
|
Bybit (futures only) supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange.
|
||||||
On futures, Bybit supports both `stop-limit` as well as `stop-market` orders. You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type to use.
|
On futures, Bybit supports both `stop-limit` as well as `stop-market` orders. You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type to use.
|
||||||
@@ -289,6 +307,41 @@ It's therefore required to pass the UID as well.
|
|||||||
!!! Warning "Necessary Verification"
|
!!! Warning "Necessary Verification"
|
||||||
Bitmart requires Verification Lvl2 to successfully trade on the spot market through the API - even though trading via UI works just fine with just Lvl1 verification.
|
Bitmart requires Verification Lvl2 to successfully trade on the spot market through the API - even though trading via UI works just fine with just Lvl1 verification.
|
||||||
|
|
||||||
|
## Hyperliquid
|
||||||
|
|
||||||
|
!!! Tip "Stoploss on Exchange"
|
||||||
|
Hyperliquid supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it.
|
||||||
|
|
||||||
|
Hyperliquid is a Decentralized Exchange (DEX). Decentralized exchanges work a bit different compared to normal exchanges. Instead of authenticating private API calls using an API key, private API calls need to be signed with the private key of your wallet (We recommend using an api Wallet for this, generated either on Hyperliquid or in your wallet of choice).
|
||||||
|
This needs to be configured like this:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"exchange": {
|
||||||
|
"name": "hyperliquid",
|
||||||
|
"walletAddress": "your_eth_wallet_address",
|
||||||
|
"privateKey": "your_api_private_key",
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* walletAddress in hex format: `0x<40 hex characters>` - Can be easily copied from your wallet - and should be your wallet address, not your API Wallet Address.
|
||||||
|
* privateKey in hex format: `0x<64 hex characters>` - Use the key the API Wallet shows on creation.
|
||||||
|
|
||||||
|
Hyperliquid handles deposits and withdrawals on the Arbitrum One chain, a Layer 2 scaling solution built on top of Ethereum. Hyperliquid uses USDC as quote / collateral. The process of depositing USDC on Hyperliquid requires a couple of steps, see [how to start trading](https://hyperliquid.gitbook.io/hyperliquid-docs/onboarding/how-to-start-trading) for details on what steps are needed.
|
||||||
|
|
||||||
|
!!! Note "Hyperliquid general usage Notes"
|
||||||
|
Hyperliquid does not support market orders, however ccxt will simulate market orders by placing limit orders with a maximum slippage of 5%.
|
||||||
|
Unfortunately, hyperliquid only offers 5000 historic candles, so backtesting will either need to build candles historically (by waiting and downloading the data incrementally over time) - or will be limited to the last 5000 candles.
|
||||||
|
|
||||||
|
!!! Info "Some general best practices (non exhaustive)"
|
||||||
|
* Beware of supply chain attacks, like pip package poisoning etcetera. Whenever you use your private key, make sure your environment is safe.
|
||||||
|
* Don't use your actual wallet private key for trading. Use the Hyperliquid [API generator](https://app.hyperliquid.xyz/API) to create a separate API wallet.
|
||||||
|
* Don't store your actual wallet private key on the server you use for freqtrade. Use the API wallet private key instead. This key won't allow withdrawals, only trading.
|
||||||
|
* Always keep your mnemonic phrase and private key private.
|
||||||
|
* Don't use the same mnemonic as the one you had to backup when initializing a hardware wallet, using the same mnemonic basically deletes the security of your hardware wallet.
|
||||||
|
* Create a different software wallet, only transfer the funds you want to trade with to that wallet, and use that wallet to trade on Hyperliquid.
|
||||||
|
* If you have funds you don't want to use for trading (after making a profit for example), transfer them back to your hardware wallet.
|
||||||
|
|
||||||
## All exchanges
|
## All exchanges
|
||||||
|
|
||||||
Should you experience constant errors with Nonce (like `InvalidNonce`), it is best to regenerate the API keys. Resetting Nonce is difficult and it's usually easier to regenerate the API keys.
|
Should you experience constant errors with Nonce (like `InvalidNonce`), it is best to regenerate the API keys. Resetting Nonce is difficult and it's usually easier to regenerate the API keys.
|
||||||
@@ -298,7 +351,7 @@ Should you experience constant errors with Nonce (like `InvalidNonce`), it is be
|
|||||||
* The Ocean (exchange id: `theocean`) exchange uses Web3 functionality and requires `web3` python package to be installed:
|
* The Ocean (exchange id: `theocean`) exchange uses Web3 functionality and requires `web3` python package to be installed:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ pip3 install web3
|
pip3 install web3
|
||||||
```
|
```
|
||||||
|
|
||||||
### Getting latest price / Incomplete candles
|
### Getting latest price / Incomplete candles
|
||||||
|
|||||||
+24
-3
@@ -40,6 +40,10 @@ This could be caused by the following reasons:
|
|||||||
* The installation did not complete successfully.
|
* The installation did not complete successfully.
|
||||||
* Please check the [Installation documentation](installation.md).
|
* Please check the [Installation documentation](installation.md).
|
||||||
|
|
||||||
|
### The bot starts, but in STOPPED mode
|
||||||
|
|
||||||
|
Make sure you set the `initial_state` config option to `"running"` in your config.json
|
||||||
|
|
||||||
### I have waited 5 minutes, why hasn't the bot made any trades yet?
|
### I have waited 5 minutes, why hasn't the bot made any trades yet?
|
||||||
|
|
||||||
* Depending on the buy strategy, the amount of whitelisted coins, the
|
* Depending on the buy strategy, the amount of whitelisted coins, the
|
||||||
@@ -100,6 +104,19 @@ You can use the `/stopentry` command in Telegram to prevent future trade entry,
|
|||||||
|
|
||||||
Please look at the [advanced setup documentation Page](advanced-setup.md#running-multiple-instances-of-freqtrade).
|
Please look at the [advanced setup documentation Page](advanced-setup.md#running-multiple-instances-of-freqtrade).
|
||||||
|
|
||||||
|
### I'm getting "Impossible to load Strategy" when starting the bot
|
||||||
|
|
||||||
|
This error message is shown when the bot cannot load the strategy.
|
||||||
|
Usually, you can use `freqtrade list-strategies` to list all available strategies.
|
||||||
|
The output of this command will also include a status column, showing if the strategy can be loaded.
|
||||||
|
|
||||||
|
Please check the following:
|
||||||
|
|
||||||
|
* Are you using the correct strategy name? The strategy name is case-sensitive and must correspond to the Strategy class name (not the filename!).
|
||||||
|
* Is the strategy in the `user_data/strategies` directory, and has the file-ending `.py`?
|
||||||
|
* Does the bot show other warnings before this error? Maybe you're missing some dependencies for the strategy - which would be highlighted in the log.
|
||||||
|
* In case of docker - is the strategy directory mounted correctly (check the volumes part of the docker-compose file)?
|
||||||
|
|
||||||
### I'm getting "Missing data fillup" messages in the log
|
### I'm getting "Missing data fillup" messages in the log
|
||||||
|
|
||||||
This message is just a warning that the latest candles had missing candles in them.
|
This message is just a warning that the latest candles had missing candles in them.
|
||||||
@@ -116,6 +133,10 @@ This message is a warning that the candles had a price jump of > 30%.
|
|||||||
This might be a sign that the pair stopped trading, and some token exchange took place (e.g. COCOS in 2021 - where price jumped from 0.0000154 to 0.01621).
|
This might be a sign that the pair stopped trading, and some token exchange took place (e.g. COCOS in 2021 - where price jumped from 0.0000154 to 0.01621).
|
||||||
This message is often accompanied by ["Missing data fillup"](#im-getting-missing-data-fillup-messages-in-the-log) - as trading on such pairs is often stopped for some time.
|
This message is often accompanied by ["Missing data fillup"](#im-getting-missing-data-fillup-messages-in-the-log) - as trading on such pairs is often stopped for some time.
|
||||||
|
|
||||||
|
### I want to reset the bot's database
|
||||||
|
|
||||||
|
To reset the bot's database, you can either delete the database (by default `tradesv3.sqlite` or `tradesv3.dryrun.sqlite`), or use a different database url via `--db-url` (e.g. `sqlite:///mynewdatabase.sqlite`).
|
||||||
|
|
||||||
### I'm getting "Outdated history for pair xxx" in the log
|
### I'm getting "Outdated history for pair xxx" in the log
|
||||||
|
|
||||||
The bot is trying to tell you that it got an outdated last candle (not the last complete candle).
|
The bot is trying to tell you that it got an outdated last candle (not the last complete candle).
|
||||||
@@ -146,9 +167,9 @@ The same fix should be applied in the configuration file, if order types are def
|
|||||||
|
|
||||||
### I'm trying to start the bot live, but get an API permission error
|
### I'm trying to start the bot live, but get an API permission error
|
||||||
|
|
||||||
Errors like `Invalid API-key, IP, or permissions for action` mean exactly what they actually say.
|
Errors like `Invalid API-key, IP, or permissions for action` mean exactly what they actually say.
|
||||||
Your API key is either invalid (copy/paste error? check for leading/trailing spaces in the config), expired, or the IP you're running the bot from is not enabled in the Exchange's API console.
|
Your API key is either invalid (copy/paste error? check for leading/trailing spaces in the config), expired, or the IP you're running the bot from is not enabled in the Exchange's API console.
|
||||||
Usually, the permission "Spot Trading" (or the equivalent in the exchange you use) will be necessary.
|
Usually, the permission "Spot Trading" (or the equivalent in the exchange you use) will be necessary.
|
||||||
Futures will usually have to be enabled specifically.
|
Futures will usually have to be enabled specifically.
|
||||||
|
|
||||||
### How do I search the bot logs for something?
|
### How do I search the bot logs for something?
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ The plot configuration can be accessed via the "Plot Configurator" (Cog icon) bu
|
|||||||
|
|
||||||
### Settings
|
### Settings
|
||||||
|
|
||||||
|
|
||||||
Several UI related settings can be changed by accessing the settings page.
|
Several UI related settings can be changed by accessing the settings page.
|
||||||
|
|
||||||
Things you can change (among others):
|
Things you can change (among others):
|
||||||
|
|||||||
@@ -293,10 +293,10 @@ class MyCoolPyTorchClassifier(BasePyTorchClassifier):
|
|||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
config = self.freqai_info.get("model_training_parameters", {})
|
config = self.freqai_info.get("model_training_parameters", {})
|
||||||
self.learning_rate: float = config.get("learning_rate", 3e-4)
|
self.learning_rate: float = config.get("learning_rate", 3e-4)
|
||||||
self.model_kwargs: Dict[str, Any] = config.get("model_kwargs", {})
|
self.model_kwargs: dict[str, Any] = config.get("model_kwargs", {})
|
||||||
self.trainer_kwargs: Dict[str, Any] = config.get("trainer_kwargs", {})
|
self.trainer_kwargs: dict[str, Any] = config.get("trainer_kwargs", {})
|
||||||
|
|
||||||
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
|
def fit(self, data_dictionary: dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
|
||||||
"""
|
"""
|
||||||
User sets up the training and test data to fit their desired model here
|
User sets up the training and test data to fit their desired model here
|
||||||
:param data_dictionary: the dictionary holding all data for train, test,
|
:param data_dictionary: the dictionary holding all data for train, test,
|
||||||
@@ -359,10 +359,10 @@ class PyTorchMLPRegressor(BasePyTorchRegressor):
|
|||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
config = self.freqai_info.get("model_training_parameters", {})
|
config = self.freqai_info.get("model_training_parameters", {})
|
||||||
self.learning_rate: float = config.get("learning_rate", 3e-4)
|
self.learning_rate: float = config.get("learning_rate", 3e-4)
|
||||||
self.model_kwargs: Dict[str, Any] = config.get("model_kwargs", {})
|
self.model_kwargs: dict[str, Any] = config.get("model_kwargs", {})
|
||||||
self.trainer_kwargs: Dict[str, Any] = config.get("trainer_kwargs", {})
|
self.trainer_kwargs: dict[str, Any] = config.get("trainer_kwargs", {})
|
||||||
|
|
||||||
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
|
def fit(self, data_dictionary: dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
|
||||||
n_features = data_dictionary["train_features"].shape[-1]
|
n_features = data_dictionary["train_features"].shape[-1]
|
||||||
model = PyTorchMLPModel(
|
model = PyTorchMLPModel(
|
||||||
input_dim=n_features,
|
input_dim=n_features,
|
||||||
@@ -393,7 +393,7 @@ Here we create a `PyTorchMLPRegressor` class that implements the `fit` method. T
|
|||||||
|
|
||||||
For example, if you are using a binary classifier to predict price movements as up or down, you can set the class names as follows:
|
For example, if you are using a binary classifier to predict price movements as up or down, you can set the class names as follows:
|
||||||
```python
|
```python
|
||||||
def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame:
|
def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
|
||||||
self.freqai.class_names = ["down", "up"]
|
self.freqai.class_names = ["down", "up"]
|
||||||
dataframe['&s-up_or_down'] = np.where(dataframe["close"].shift(-100) >
|
dataframe['&s-up_or_down'] = np.where(dataframe["close"].shift(-100) >
|
||||||
dataframe["close"], 'up', 'down')
|
dataframe["close"], 'up', 'down')
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ Mandatory parameters are marked as **Required** and have to be set in one of the
|
|||||||
| `write_metrics_to_disk` | Collect train timings, inference timings and cpu usage in json file. <br> **Datatype:** Boolean. <br> Default: `False`
|
| `write_metrics_to_disk` | Collect train timings, inference timings and cpu usage in json file. <br> **Datatype:** Boolean. <br> Default: `False`
|
||||||
| `data_kitchen_thread_count` | <br> Designate the number of threads you want to use for data processing (outlier methods, normalization, etc.). This has no impact on the number of threads used for training. If user does not set it (default), FreqAI will use max number of threads - 2 (leaving 1 physical core available for Freqtrade bot and FreqUI) <br> **Datatype:** Positive integer.
|
| `data_kitchen_thread_count` | <br> Designate the number of threads you want to use for data processing (outlier methods, normalization, etc.). This has no impact on the number of threads used for training. If user does not set it (default), FreqAI will use max number of threads - 2 (leaving 1 physical core available for Freqtrade bot and FreqUI) <br> **Datatype:** Positive integer.
|
||||||
| `activate_tensorboard` | <br> Indicate whether or not to activate tensorboard for the tensorboard enabled modules (currently Reinforcment Learning, XGBoost, Catboost, and PyTorch). Tensorboard needs Torch installed, which means you will need the torch/RL docker image or you need to answer "yes" to the install question about whether or not you wish to install Torch. <br> **Datatype:** Boolean. <br> Default: `True`.
|
| `activate_tensorboard` | <br> Indicate whether or not to activate tensorboard for the tensorboard enabled modules (currently Reinforcment Learning, XGBoost, Catboost, and PyTorch). Tensorboard needs Torch installed, which means you will need the torch/RL docker image or you need to answer "yes" to the install question about whether or not you wish to install Torch. <br> **Datatype:** Boolean. <br> Default: `True`.
|
||||||
|
| `wait_for_training_iteration_on_reload` | <br> When using /reload or ctrl-c, wait for the current training iteration to finish before completing graceful shutdown. If set to `False`, FreqAI will break the current training iteration, allowing you to shutdown gracefully more quickly, but you will lose your current training iteration. <br> **Datatype:** Boolean. <br> Default: `True`.
|
||||||
|
|
||||||
### Feature parameters
|
### Feature parameters
|
||||||
|
|
||||||
|
|||||||
+44
-24
@@ -42,11 +42,11 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
|
|||||||
[--recursive-strategy-search] [--freqaimodel NAME]
|
[--recursive-strategy-search] [--freqaimodel NAME]
|
||||||
[--freqaimodel-path PATH] [-i TIMEFRAME]
|
[--freqaimodel-path PATH] [-i TIMEFRAME]
|
||||||
[--timerange TIMERANGE]
|
[--timerange TIMERANGE]
|
||||||
[--data-format-ohlcv {json,jsongz,hdf5}]
|
[--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}]
|
||||||
[--max-open-trades INT]
|
[--max-open-trades INT]
|
||||||
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
|
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
|
||||||
[-p PAIRS [PAIRS ...]] [--hyperopt-path PATH]
|
[-p PAIRS [PAIRS ...]] [--hyperopt-path PATH]
|
||||||
[--eps] [--dmmp] [--enable-protections]
|
[--eps] [--enable-protections]
|
||||||
[--dry-run-wallet DRY_RUN_WALLET]
|
[--dry-run-wallet DRY_RUN_WALLET]
|
||||||
[--timeframe-detail TIMEFRAME_DETAIL] [-e INT]
|
[--timeframe-detail TIMEFRAME_DETAIL] [-e INT]
|
||||||
[--spaces {all,buy,sell,roi,stoploss,trailing,protection,trades,default} [{all,buy,sell,roi,stoploss,trailing,protection,trades,default} ...]]
|
[--spaces {all,buy,sell,roi,stoploss,trailing,protection,trades,default} [{all,buy,sell,roi,stoploss,trailing,protection,trades,default} ...]]
|
||||||
@@ -55,15 +55,15 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
|
|||||||
[--hyperopt-loss NAME] [--disable-param-export]
|
[--hyperopt-loss NAME] [--disable-param-export]
|
||||||
[--ignore-missing-spaces] [--analyze-per-epoch]
|
[--ignore-missing-spaces] [--analyze-per-epoch]
|
||||||
|
|
||||||
optional arguments:
|
options:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
-i TIMEFRAME, --timeframe TIMEFRAME
|
-i TIMEFRAME, --timeframe TIMEFRAME
|
||||||
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
|
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
|
||||||
--timerange TIMERANGE
|
--timerange TIMERANGE
|
||||||
Specify what timerange of data to use.
|
Specify what timerange of data to use.
|
||||||
--data-format-ohlcv {json,jsongz,hdf5}
|
--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}
|
||||||
Storage format for downloaded candle (OHLCV) data.
|
Storage format for downloaded candle (OHLCV) data.
|
||||||
(default: `json`).
|
(default: `feather`).
|
||||||
--max-open-trades INT
|
--max-open-trades INT
|
||||||
Override the value of the `max_open_trades`
|
Override the value of the `max_open_trades`
|
||||||
configuration setting.
|
configuration setting.
|
||||||
@@ -80,10 +80,6 @@ optional arguments:
|
|||||||
--eps, --enable-position-stacking
|
--eps, --enable-position-stacking
|
||||||
Allow buying the same pair multiple times (position
|
Allow buying the same pair multiple times (position
|
||||||
stacking).
|
stacking).
|
||||||
--dmmp, --disable-max-market-positions
|
|
||||||
Disable applying `max_open_trades` during backtest
|
|
||||||
(same as setting `max_open_trades` to a very high
|
|
||||||
number).
|
|
||||||
--enable-protections, --enableprotections
|
--enable-protections, --enableprotections
|
||||||
Enable protections for backtesting.Will slow
|
Enable protections for backtesting.Will slow
|
||||||
backtesting down by a considerable amount, but will
|
backtesting down by a considerable amount, but will
|
||||||
@@ -133,7 +129,8 @@ optional arguments:
|
|||||||
|
|
||||||
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).
|
||||||
--logfile FILE Log to the file specified. Special values are:
|
--logfile FILE, --log-file FILE
|
||||||
|
Log to the file specified. Special values are:
|
||||||
'syslog', 'journald'. See the documentation for more
|
'syslog', 'journald'. See the documentation for more
|
||||||
details.
|
details.
|
||||||
-V, --version show program's version number and exit
|
-V, --version show program's version number and exit
|
||||||
@@ -142,7 +139,7 @@ Common arguments:
|
|||||||
`userdir/config.json` or `config.json` whichever
|
`userdir/config.json` or `config.json` whichever
|
||||||
exists). Multiple --config options may be used. Can be
|
exists). Multiple --config options may be used. Can be
|
||||||
set to `-` to read config from stdin.
|
set to `-` to read config from stdin.
|
||||||
-d PATH, --datadir PATH
|
-d PATH, --datadir PATH, --data-dir PATH
|
||||||
Path to directory with historical backtesting data.
|
Path to directory with historical backtesting data.
|
||||||
--userdir PATH, --user-data-dir PATH
|
--userdir PATH, --user-data-dir PATH
|
||||||
Path to userdata directory.
|
Path to userdata directory.
|
||||||
@@ -445,7 +442,6 @@ While this strategy is most likely too simple to provide consistent profit, it s
|
|||||||
|
|
||||||
Whether you are using `.range` functionality or the alternatives above, you should try to use space ranges as small as possible since this will improve CPU/RAM usage.
|
Whether you are using `.range` functionality or the alternatives above, you should try to use space ranges as small as possible since this will improve CPU/RAM usage.
|
||||||
|
|
||||||
|
|
||||||
## Optimizing protections
|
## Optimizing protections
|
||||||
|
|
||||||
Freqtrade can also optimize protections. How you optimize protections is up to you, and the following should be considered as example only.
|
Freqtrade can also optimize protections. How you optimize protections is up to you, and the following should be considered as example only.
|
||||||
@@ -589,14 +585,15 @@ Currently, the following loss functions are builtin:
|
|||||||
|
|
||||||
* `ShortTradeDurHyperOptLoss` - (default legacy Freqtrade hyperoptimization loss function) - Mostly for short trade duration and avoiding losses.
|
* `ShortTradeDurHyperOptLoss` - (default legacy Freqtrade hyperoptimization loss function) - Mostly for short trade duration and avoiding losses.
|
||||||
* `OnlyProfitHyperOptLoss` - takes only amount of profit into consideration.
|
* `OnlyProfitHyperOptLoss` - takes only amount of profit into consideration.
|
||||||
* `SharpeHyperOptLoss` - optimizes Sharpe Ratio calculated on trade returns relative to standard deviation.
|
* `SharpeHyperOptLoss` - Optimizes Sharpe Ratio calculated on trade returns relative to standard deviation.
|
||||||
* `SharpeHyperOptLossDaily` - optimizes Sharpe Ratio calculated on **daily** trade returns relative to standard deviation.
|
* `SharpeHyperOptLossDaily` - Optimizes Sharpe Ratio calculated on **daily** trade returns relative to standard deviation.
|
||||||
* `SortinoHyperOptLoss` - optimizes Sortino Ratio calculated on trade returns relative to **downside** standard deviation.
|
* `SortinoHyperOptLoss` - Optimizes Sortino Ratio calculated on trade returns relative to **downside** standard deviation.
|
||||||
* `SortinoHyperOptLossDaily` - optimizes Sortino Ratio calculated on **daily** trade returns relative to **downside** standard deviation.
|
* `SortinoHyperOptLossDaily` - optimizes Sortino Ratio calculated on **daily** trade returns relative to **downside** standard deviation.
|
||||||
* `MaxDrawDownHyperOptLoss` - Optimizes Maximum absolute drawdown.
|
* `MaxDrawDownHyperOptLoss` - Optimizes Maximum absolute drawdown.
|
||||||
* `MaxDrawDownRelativeHyperOptLoss` - Optimizes both maximum absolute drawdown while also adjusting for maximum relative drawdown.
|
* `MaxDrawDownRelativeHyperOptLoss` - Optimizes both maximum absolute drawdown while also adjusting for maximum relative drawdown.
|
||||||
* `CalmarHyperOptLoss` - Optimizes Calmar Ratio calculated on trade returns relative to max drawdown.
|
* `CalmarHyperOptLoss` - Optimizes Calmar Ratio calculated on trade returns relative to max drawdown.
|
||||||
* `ProfitDrawDownHyperOptLoss` - Optimizes by max Profit & min Drawdown objective. `DRAWDOWN_MULT` variable within the hyperoptloss file can be adjusted to be stricter or more flexible on drawdown purposes.
|
* `ProfitDrawDownHyperOptLoss` - Optimizes by max Profit & min Drawdown objective. `DRAWDOWN_MULT` variable within the hyperoptloss file can be adjusted to be stricter or more flexible on drawdown purposes.
|
||||||
|
* `MultiMetricHyperOptLoss` - Optimizes by several key metrics to achieve balanced performance. The primary focus is on maximizing Profit and minimizing Drawdown, while also considering additional metrics such as Profit Factor, Expectancy Ratio and Winrate. Moreover, it applies a penalty for epochs with a low number of trades, encouraging strategies with adequate trade frequency.
|
||||||
|
|
||||||
Creation of a custom loss function is covered in the [Advanced Hyperopt](advanced-hyperopt.md) part of the documentation.
|
Creation of a custom loss function is covered in the [Advanced Hyperopt](advanced-hyperopt.md) part of the documentation.
|
||||||
|
|
||||||
@@ -867,18 +864,15 @@ You can use the `--print-all` command line option if you would like to see all r
|
|||||||
|
|
||||||
## Position stacking and disabling max market positions
|
## Position stacking and disabling max market positions
|
||||||
|
|
||||||
In some situations, you may need to run Hyperopt (and Backtesting) with the
|
In some situations, you may need to run Hyperopt (and Backtesting) with the `--eps`/`--enable-position-staking` argument, or you may need to set `max_open_trades` to a very high number to disable the limit on the number of open trades.
|
||||||
`--eps`/`--enable-position-staking` and `--dmmp`/`--disable-max-market-positions` arguments.
|
|
||||||
|
|
||||||
By default, hyperopt emulates the behavior of the Freqtrade Live Run/Dry Run, where only one
|
By default, hyperopt emulates the behavior of the Freqtrade Live Run/Dry Run, where only one
|
||||||
open trade is allowed for every traded pair. The total number of trades open for all pairs
|
open trade per pair is allowed. The total number of trades open for all pairs
|
||||||
is also limited by the `max_open_trades` setting. During Hyperopt/Backtesting this may lead to
|
is also limited by the `max_open_trades` setting. During Hyperopt/Backtesting this may lead to
|
||||||
some potential trades to be hidden (or masked) by previously open trades.
|
potential trades being hidden (or masked) by already open trades.
|
||||||
|
|
||||||
The `--eps`/`--enable-position-stacking` argument allows emulation of buying the same pair multiple times,
|
The `--eps`/`--enable-position-stacking` argument allows emulation of buying the same pair multiple times.
|
||||||
while `--dmmp`/`--disable-max-market-positions` disables applying `max_open_trades`
|
Using `--max-open-trades` with a very high number will disable the limit on the number of open trades.
|
||||||
during Hyperopt/Backtesting (which is equal to setting `max_open_trades` to a very high
|
|
||||||
number).
|
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
Dry/live runs will **NOT** use position stacking - therefore it does make sense to also validate the strategy without this as it's closer to reality.
|
Dry/live runs will **NOT** use position stacking - therefore it does make sense to also validate the strategy without this as it's closer to reality.
|
||||||
@@ -919,13 +913,39 @@ Your epochs should therefore be aligned to the possible values - or you should b
|
|||||||
|
|
||||||
After you run Hyperopt for the desired amount of epochs, you can later list all results for analysis, select only best or profitable once, and show the details for any of the epochs previously evaluated. This can be done with the `hyperopt-list` and `hyperopt-show` sub-commands. The usage of these sub-commands is described in the [Utils](utils.md#list-hyperopt-results) chapter.
|
After you run Hyperopt for the desired amount of epochs, you can later list all results for analysis, select only best or profitable once, and show the details for any of the epochs previously evaluated. This can be done with the `hyperopt-list` and `hyperopt-show` sub-commands. The usage of these sub-commands is described in the [Utils](utils.md#list-hyperopt-results) chapter.
|
||||||
|
|
||||||
|
## Output debug messages from your strategy
|
||||||
|
|
||||||
|
If you want to output debug messages from your strategy, you can use the `logging` module. By default, Freqtrade will output all messages with a level of `INFO` or higher.
|
||||||
|
|
||||||
|
|
||||||
|
``` python
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class MyAwesomeStrategy(IStrategy):
|
||||||
|
...
|
||||||
|
|
||||||
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
logger.info("This is a debug message")
|
||||||
|
...
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! Note "using print"
|
||||||
|
Messages printed via `print()` will not be shown in the hyperopt output unless parallelism is disabled (`-j 1`).
|
||||||
|
It is recommended to use the `logging` module instead.
|
||||||
|
|
||||||
## Validate backtesting results
|
## Validate backtesting results
|
||||||
|
|
||||||
Once the optimized strategy has been implemented into your strategy, you should backtest this strategy to make sure everything is working as expected.
|
Once the optimized strategy has been implemented into your strategy, you should backtest this strategy to make sure everything is working as expected.
|
||||||
|
|
||||||
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 for Backtesting.
|
||||||
|
|
||||||
### Why do my backtest results not match my hyperopt results?
|
### Why do my backtest results not match my hyperopt results?
|
||||||
|
|
||||||
Should results not match, check the following factors:
|
Should results not match, check the following factors:
|
||||||
|
|
||||||
* 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).
|
* 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).
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
Pairlist Handlers define the list of pairs (pairlist) that the bot should trade. They are configured in the `pairlists` section of the configuration settings.
|
Pairlist Handlers define the list of pairs (pairlist) that the bot should trade. They are configured in the `pairlists` section of the configuration settings.
|
||||||
|
|
||||||
In your configuration, you can use Static Pairlist (defined by the [`StaticPairList`](#static-pair-list) Pairlist Handler) and Dynamic Pairlist (defined by the [`VolumePairList`](#volume-pair-list) Pairlist Handler).
|
In your configuration, you can use Static Pairlist (defined by the [`StaticPairList`](#static-pair-list) Pairlist Handler) and Dynamic Pairlist (defined by the [`VolumePairList`](#volume-pair-list) and [`PercentChangePairList`](#percent-change-pair-list) Pairlist Handlers).
|
||||||
|
|
||||||
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 can define either `StaticPairList`, `VolumePairList`, `ProducerPairList`, `RemotePairList` or `MarketCapPairList` 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`, `MarketCapPairList` or `PercentChangePairList` 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.
|
||||||
|
|
||||||
@@ -22,6 +22,7 @@ You may also use something like `.*DOWN/BTC` or `.*UP/BTC` to exclude leveraged
|
|||||||
|
|
||||||
* [`StaticPairList`](#static-pair-list) (default, if not configured differently)
|
* [`StaticPairList`](#static-pair-list) (default, if not configured differently)
|
||||||
* [`VolumePairList`](#volume-pair-list)
|
* [`VolumePairList`](#volume-pair-list)
|
||||||
|
* [`PercentChangePairList`](#percent-change-pair-list)
|
||||||
* [`ProducerPairList`](#producerpairlist)
|
* [`ProducerPairList`](#producerpairlist)
|
||||||
* [`RemotePairList`](#remotepairlist)
|
* [`RemotePairList`](#remotepairlist)
|
||||||
* [`MarketCapPairList`](#marketcappairlist)
|
* [`MarketCapPairList`](#marketcappairlist)
|
||||||
@@ -54,7 +55,6 @@ It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklis
|
|||||||
By default, only currently enabled pairs are allowed.
|
By default, only currently enabled pairs are allowed.
|
||||||
To skip pair validation against active markets, set `"allow_inactive": true` within the `StaticPairList` configuration.
|
To skip pair validation against active markets, set `"allow_inactive": true` within the `StaticPairList` configuration.
|
||||||
This can be useful for backtesting expired pairs (like quarterly spot-markets).
|
This can be useful for backtesting expired pairs (like quarterly spot-markets).
|
||||||
This option must be configured along with `exchange.skip_pair_validation` in the exchange configuration.
|
|
||||||
|
|
||||||
When used in a "follow-up" position (e.g. after VolumePairlist), all pairs in `'pair_whitelist'` will be added to the end of the pairlist.
|
When used in a "follow-up" position (e.g. after VolumePairlist), all pairs in `'pair_whitelist'` will be added to the end of the pairlist.
|
||||||
|
|
||||||
@@ -152,6 +152,89 @@ More sophisticated approach can be used, by using `lookback_timeframe` for candl
|
|||||||
!!! Note
|
!!! Note
|
||||||
`VolumePairList` does not support backtesting mode.
|
`VolumePairList` does not support backtesting mode.
|
||||||
|
|
||||||
|
#### Percent Change Pair List
|
||||||
|
|
||||||
|
`PercentChangePairList` filters and sorts pairs based on the percentage change in their price over the last 24 hours or any defined timeframe as part of advanced options. This allows traders to focus on assets that have experienced significant price movements, either positive or negative.
|
||||||
|
|
||||||
|
**Configuration Options**
|
||||||
|
|
||||||
|
* `number_assets`: Specifies the number of top pairs to select based on the 24-hour percentage change.
|
||||||
|
* `min_value`: Sets a minimum percentage change threshold. Pairs with a percentage change below this value will be filtered out.
|
||||||
|
* `max_value`: Sets a maximum percentage change threshold. Pairs with a percentage change above this value will be filtered out.
|
||||||
|
* `sort_direction`: Specifies the order in which pairs are sorted based on their percentage change. Accepts two values: `asc` for ascending order and `desc` for descending order.
|
||||||
|
* `refresh_period`: Defines the interval (in seconds) at which the pairlist will be refreshed. The default is 1800 seconds (30 minutes).
|
||||||
|
* `lookback_days`: Number of days to look back. When `lookback_days` is selected, the `lookback_timeframe` is defaulted to 1 day.
|
||||||
|
* `lookback_timeframe`: Timeframe to use for the lookback period.
|
||||||
|
* `lookback_period`: Number of periods to look back at.
|
||||||
|
|
||||||
|
When PercentChangePairList is used after other Pairlist Handlers, it will operate on the outputs of those handlers. If it is the leading Pairlist Handler, it will select pairs from all available markets with the specified stake currency.
|
||||||
|
|
||||||
|
`PercentChangePairList` uses ticker data from the exchange, provided via the ccxt library:
|
||||||
|
The percentage change is calculated as the change in price over the last 24 hours.
|
||||||
|
|
||||||
|
??? Note "Unsupported exchanges"
|
||||||
|
On some exchanges (like HTX), regular PercentChangePairList does not work as the api does not natively provide 24h percent change in price. This can be worked around by using candle data to calculate the percentage change. To roughly simulate 24h percent change, you can use the following configuration. Please note that these pairlists will only refresh once per day.
|
||||||
|
```json
|
||||||
|
"pairlists": [
|
||||||
|
{
|
||||||
|
"method": "PercentChangePairList",
|
||||||
|
"number_assets": 20,
|
||||||
|
"min_value": 0,
|
||||||
|
"refresh_period": 86400,
|
||||||
|
"lookback_days": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example Configuration to Read from Ticker**
|
||||||
|
|
||||||
|
```json
|
||||||
|
"pairlists": [
|
||||||
|
{
|
||||||
|
"method": "PercentChangePairList",
|
||||||
|
"number_assets": 15,
|
||||||
|
"min_value": -10,
|
||||||
|
"max_value": 50
|
||||||
|
}
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
In this configuration:
|
||||||
|
|
||||||
|
1. The top 15 pairs are selected based on the highest percentage change in price over the last 24 hours.
|
||||||
|
2. Only pairs with a percentage change between -10% and 50% are considered.
|
||||||
|
|
||||||
|
**Example Configuration to Read from Candles**
|
||||||
|
|
||||||
|
```json
|
||||||
|
"pairlists": [
|
||||||
|
{
|
||||||
|
"method": "PercentChangePairList",
|
||||||
|
"number_assets": 15,
|
||||||
|
"sort_key": "percentage",
|
||||||
|
"min_value": 0,
|
||||||
|
"refresh_period": 3600,
|
||||||
|
"lookback_timeframe": "1h",
|
||||||
|
"lookback_period": 72
|
||||||
|
}
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
This example builds the percent change pairs based on a rolling period of 3 days of 1-hour candles by using `lookback_timeframe` for candle size and `lookback_period` which specifies the number of candles.
|
||||||
|
|
||||||
|
The percent change in price is calculated using the following formula, which expresses the percentage difference between the current candle's close price and the previous candle's close price, as defined by the specified timeframe and lookback period:
|
||||||
|
|
||||||
|
$$ Percent Change = (\frac{Current Close - Previous Close}{Previous Close}) * 100 $$
|
||||||
|
|
||||||
|
!!! Warning "Range look back and refresh period"
|
||||||
|
When used in conjunction with `lookback_days` and `lookback_timeframe` the `refresh_period` can not be smaller than the candle size in seconds. As this will result in unnecessary requests to the exchanges API.
|
||||||
|
|
||||||
|
!!! Warning "Performance implications when using lookback range"
|
||||||
|
If used in first position in combination with lookback, the computation of the range-based percent change can be time and resource consuming, as it downloads candles for all tradable pairs. Hence it's highly advised to use the standard approach with `PercentChangePairList` to narrow the pairlist down for further percent-change calculation.
|
||||||
|
|
||||||
|
!!! Note "Backtesting"
|
||||||
|
`PercentChangePairList` does not support backtesting mode.
|
||||||
|
|
||||||
#### ProducerPairList
|
#### ProducerPairList
|
||||||
|
|
||||||
With `ProducerPairList`, you can reuse the pairlist from a [Producer](producer-consumer.md) without explicitly defining the pairlist on each consumer.
|
With `ProducerPairList`, you can reuse the pairlist from a [Producer](producer-consumer.md) without explicitly defining the pairlist on each consumer.
|
||||||
@@ -269,7 +352,7 @@ The optional `bearer_token` will be included in the requests Authorization Heade
|
|||||||
|
|
||||||
#### MarketCapPairList
|
#### 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.
|
`MarketCapPairList` employs sorting/filtering of pairs by their marketcap rank based of CoinGecko. The returned pairlist will be sorted based of their marketcap ranks.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"pairlists": [
|
"pairlists": [
|
||||||
@@ -277,14 +360,22 @@ The optional `bearer_token` will be included in the requests Authorization Heade
|
|||||||
"method": "MarketCapPairList",
|
"method": "MarketCapPairList",
|
||||||
"number_assets": 20,
|
"number_assets": 20,
|
||||||
"max_rank": 50,
|
"max_rank": 50,
|
||||||
"refresh_period": 86400
|
"refresh_period": 86400,
|
||||||
|
"categories": ["layer-1"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
`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.
|
`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.
|
||||||
|
While using a `max_rank` bigger than 250 is supported, it's not recommended, as it'll cause multiple API calls to CoinGecko, which can lead to rate limit issues.
|
||||||
|
|
||||||
`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).
|
The `refresh_period` setting defines the interval (in seconds) at which the marketcap rank data will be refreshed. The default is 86,400 seconds (1 day). The pairlist cache (`refresh_period`) applies to both generating pairlists (when in the first position in the list) and filtering instances (when not in the first position in the list).
|
||||||
|
|
||||||
|
The `categories` setting specifies the [coingecko categories](https://www.coingecko.com/en/categories) from which to select coins from. The default is an empty list `[]`, meaning no category filtering is applied.
|
||||||
|
If an incorrect category string is chosen, the plugin will print the available categories from CoinGecko and fail. The category should be the ID of the category, for example, for `https://www.coingecko.com/en/categories/layer-1`, the category ID would be `layer-1`. You can pass multiple categories such as `["layer-1", "meme-token"]` to select from several categories.
|
||||||
|
|
||||||
|
!!! Warning "Many categories"
|
||||||
|
Each added category corresponds to one API call to CoinGecko. The more categories you add, the longer the pairlist generation will take, potentially causing rate limit issues.
|
||||||
|
|
||||||
#### AgeFilter
|
#### AgeFilter
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,16 @@
|
|||||||
## Protections
|
## Protections
|
||||||
|
|
||||||
!!! Warning "Beta feature"
|
|
||||||
This feature is still in it's testing phase. Should you notice something you think is wrong please let us know via Discord or via Github Issue.
|
|
||||||
|
|
||||||
Protections will protect your strategy from unexpected events and market conditions by temporarily stop trading for either one pair, or for all pairs.
|
Protections will protect your strategy from unexpected events and market conditions by temporarily stop trading for either one pair, or for all pairs.
|
||||||
All protection end times are rounded up to the next candle to avoid sudden, unexpected intra-candle buys.
|
All protection end times are rounded up to the next candle to avoid sudden, unexpected intra-candle buys.
|
||||||
|
|
||||||
!!! Note
|
!!! Tip "Usage tips"
|
||||||
Not all Protections will work for all strategies, and parameters will need to be tuned for your strategy to improve performance.
|
Not all Protections will work for all strategies, and parameters will need to be tuned for your strategy to improve performance.
|
||||||
|
|
||||||
!!! Tip
|
|
||||||
Each Protection can be configured multiple times with different parameters, to allow different levels of protection (short-term / long-term).
|
Each Protection can be configured multiple times with different parameters, to allow different levels of protection (short-term / long-term).
|
||||||
|
|
||||||
!!! Note "Backtesting"
|
!!! Note "Backtesting"
|
||||||
Protections are supported by backtesting and hyperopt, but must be explicitly enabled by using the `--enable-protections` flag.
|
Protections are supported by backtesting and hyperopt, but must be explicitly enabled by using the `--enable-protections` flag.
|
||||||
|
|
||||||
!!! Warning "Setting protections from the configuration"
|
|
||||||
Setting protections from the configuration via `"protections": [],` key should be considered deprecated and will be removed in a future version.
|
|
||||||
It is also no longer guaranteed that your protections apply to the strategy in cases where the strategy defines [protections as property](hyperopt.md#optimizing-protections).
|
|
||||||
|
|
||||||
### Available Protections
|
### Available Protections
|
||||||
|
|
||||||
* [`StoplossGuard`](#stoploss-guard) Stop trading if a certain amount of stoploss occurred within a certain time window.
|
* [`StoplossGuard`](#stoploss-guard) Stop trading if a certain amount of stoploss occurred within a certain time window.
|
||||||
@@ -36,6 +28,7 @@ All protection end times are rounded up to the next candle to avoid sudden, unex
|
|||||||
| `lookback_period_candles` | Only trades that completed within the last `lookback_period_candles` candles will be considered. This setting may be ignored by some Protections. <br> **Datatype:** Positive integer (in candles).
|
| `lookback_period_candles` | Only trades that completed within the last `lookback_period_candles` candles will be considered. This setting may be ignored by some Protections. <br> **Datatype:** Positive integer (in candles).
|
||||||
| `lookback_period` | Only trades that completed after `current_time - lookback_period` will be considered. <br>Cannot be used together with `lookback_period_candles`. <br>This setting may be ignored by some Protections. <br> **Datatype:** Float (in minutes)
|
| `lookback_period` | Only trades that completed after `current_time - lookback_period` will be considered. <br>Cannot be used together with `lookback_period_candles`. <br>This setting may be ignored by some Protections. <br> **Datatype:** Float (in minutes)
|
||||||
| `trade_limit` | Number of trades required at minimum (not used by all Protections). <br> **Datatype:** Positive integer
|
| `trade_limit` | Number of trades required at minimum (not used by all Protections). <br> **Datatype:** Positive integer
|
||||||
|
| `unlock_at` | Time when trading will be unlocked regularly (not used by all Protections). <br> **Datatype:** string <br>**Input Format:** "HH:MM" (24-hours)
|
||||||
|
|
||||||
!!! Note "Durations"
|
!!! Note "Durations"
|
||||||
Durations (`stop_duration*` and `lookback_period*` can be defined in either minutes or candles).
|
Durations (`stop_duration*` and `lookback_period*` can be defined in either minutes or candles).
|
||||||
@@ -44,7 +37,7 @@ All protection end times are rounded up to the next candle to avoid sudden, unex
|
|||||||
#### Stoploss Guard
|
#### Stoploss Guard
|
||||||
|
|
||||||
`StoplossGuard` selects all trades within `lookback_period` in minutes (or in candles when using `lookback_period_candles`).
|
`StoplossGuard` selects all trades within `lookback_period` in minutes (or in candles when using `lookback_period_candles`).
|
||||||
If `trade_limit` or more trades resulted in stoploss, trading will stop for `stop_duration` in minutes (or in candles when using `stop_duration_candles`).
|
If `trade_limit` or more trades resulted in stoploss, trading will stop for `stop_duration` in minutes (or in candles when using `stop_duration_candles`, or until the set time when using `unlock_at`).
|
||||||
|
|
||||||
This applies across all pairs, unless `only_per_pair` is set to true, which will then only look at one pair at a time.
|
This applies across all pairs, unless `only_per_pair` is set to true, which will then only look at one pair at a time.
|
||||||
|
|
||||||
@@ -97,7 +90,7 @@ def protections(self):
|
|||||||
#### Low Profit Pairs
|
#### Low Profit Pairs
|
||||||
|
|
||||||
`LowProfitPairs` uses all trades for a pair within `lookback_period` in minutes (or in candles when using `lookback_period_candles`) to determine the overall profit ratio.
|
`LowProfitPairs` uses all trades for a pair within `lookback_period` in minutes (or in candles when using `lookback_period_candles`) to determine the overall profit ratio.
|
||||||
If that ratio is below `required_profit`, that pair will be locked for `stop_duration` in minutes (or in candles when using `stop_duration_candles`).
|
If that ratio is below `required_profit`, that pair will be locked for `stop_duration` in minutes (or in candles when using `stop_duration_candles`, or until the set time when using `unlock_at`).
|
||||||
|
|
||||||
For futures bots, setting `only_per_side` will make the bot only consider one side, and will then only lock this one side, allowing for example shorts to continue after a series of long losses.
|
For futures bots, setting `only_per_side` will make the bot only consider one side, and will then only lock this one side, allowing for example shorts to continue after a series of long losses.
|
||||||
|
|
||||||
@@ -120,7 +113,7 @@ def protections(self):
|
|||||||
|
|
||||||
#### Cooldown Period
|
#### Cooldown Period
|
||||||
|
|
||||||
`CooldownPeriod` locks a pair for `stop_duration` in minutes (or in candles when using `stop_duration_candles`) after selling, avoiding a re-entry for this pair for `stop_duration` minutes.
|
`CooldownPeriod` locks a pair for `stop_duration` in minutes (or in candles when using `stop_duration_candles`, or until the set time when using `unlock_at`) after exiting, avoiding a re-entry for this pair for `stop_duration` minutes.
|
||||||
|
|
||||||
The below example will stop trading a pair for 2 candles after closing a trade, allowing this pair to "cool down".
|
The below example will stop trading a pair for 2 candles after closing a trade, allowing this pair to "cool down".
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
## Imports necessary for a strategy
|
||||||
|
|
||||||
|
When creating a strategy, you will need to import the necessary modules and classes. The following imports are required for a strategy:
|
||||||
|
|
||||||
|
By default, we recommend the following imports as a base line for your strategy:
|
||||||
|
This will cover all imports necessary for freqtrade functions to work.
|
||||||
|
Obviously you can add more imports as needed for your strategy.
|
||||||
|
|
||||||
|
``` python
|
||||||
|
# flake8: noqa: F401
|
||||||
|
# isort: skip_file
|
||||||
|
# --- Do not remove these imports ---
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
from pandas import DataFrame
|
||||||
|
from typing import Dict, Optional, Union, Tuple
|
||||||
|
|
||||||
|
from freqtrade.strategy import (
|
||||||
|
IStrategy,
|
||||||
|
Trade,
|
||||||
|
Order,
|
||||||
|
PairLocks,
|
||||||
|
informative, # @informative decorator
|
||||||
|
# Hyperopt Parameters
|
||||||
|
BooleanParameter,
|
||||||
|
CategoricalParameter,
|
||||||
|
DecimalParameter,
|
||||||
|
IntParameter,
|
||||||
|
RealParameter,
|
||||||
|
# timeframe helpers
|
||||||
|
timeframe_to_minutes,
|
||||||
|
timeframe_to_next_date,
|
||||||
|
timeframe_to_prev_date,
|
||||||
|
# Strategy helper functions
|
||||||
|
merge_informative_pair,
|
||||||
|
stoploss_from_absolute,
|
||||||
|
stoploss_from_open,
|
||||||
|
)
|
||||||
|
|
||||||
|
# --------------------------------
|
||||||
|
# Add your lib to import here
|
||||||
|
import talib.abstract as ta
|
||||||
|
from technical import qtpylib
|
||||||
|
```
|
||||||
+11
-7
@@ -28,7 +28,7 @@ Freqtrade is a free and open source crypto trading bot written in Python. It is
|
|||||||
- Develop your Strategy: Write your strategy in python, using [pandas](https://pandas.pydata.org/). Example strategies to inspire you are available in the [strategy repository](https://github.com/freqtrade/freqtrade-strategies).
|
- Develop your Strategy: Write your strategy in python, using [pandas](https://pandas.pydata.org/). Example strategies to inspire you are available in the [strategy repository](https://github.com/freqtrade/freqtrade-strategies).
|
||||||
- Download market data: Download historical data of the exchange and the markets your may want to trade with.
|
- Download market data: Download historical data of the exchange and the markets your may want to trade with.
|
||||||
- Backtest: Test your strategy on downloaded historical data.
|
- Backtest: Test your strategy on downloaded historical data.
|
||||||
- Optimize: Find the best parameters for your strategy using hyperoptimization which employs machining learning methods. You can optimize buy, sell, take profit (ROI), stop-loss and trailing stop-loss parameters for your strategy.
|
- Optimize: Find the best parameters for your strategy using hyperoptimization which employs machine learning methods. You can optimize buy, sell, take profit (ROI), stop-loss and trailing stop-loss parameters for your strategy.
|
||||||
- Select markets: Create your static list or use an automatic one based on top traded volumes and/or prices (not available during backtesting). You can also explicitly blacklist markets you don't want to trade.
|
- Select markets: Create your static list or use an automatic one based on top traded volumes and/or prices (not available during backtesting). You can also explicitly blacklist markets you don't want to trade.
|
||||||
- Run: Test your strategy with simulated money (Dry-Run mode) or deploy it with real money (Live-Trade mode).
|
- Run: Test your strategy with simulated money (Dry-Run mode) or deploy it with real money (Live-Trade mode).
|
||||||
- Run using Edge (optional module): The concept is to find the best historical [trade expectancy](edge.md#expectancy) by markets based on variation of the stop-loss and then allow/reject markets to trade. The sizing of the trade is based on a risk of a percentage of your capital.
|
- Run using Edge (optional module): The concept is to find the best historical [trade expectancy](edge.md#expectancy) by markets based on variation of the stop-loss and then allow/reject markets to trade. The sizing of the trade is based on a risk of a percentage of your capital.
|
||||||
@@ -40,20 +40,24 @@ Freqtrade is a free and open source crypto trading bot written in Python. It is
|
|||||||
Please read the [exchange specific notes](exchanges.md) to learn about eventual, special configurations needed for each exchange.
|
Please read the [exchange specific notes](exchanges.md) to learn about eventual, special configurations needed for each exchange.
|
||||||
|
|
||||||
- [X] [Binance](https://www.binance.com/)
|
- [X] [Binance](https://www.binance.com/)
|
||||||
- [X] [Bitmart](https://bitmart.com/)
|
|
||||||
- [X] [BingX](https://bingx.com/invite/0EM9RX)
|
- [X] [BingX](https://bingx.com/invite/0EM9RX)
|
||||||
|
- [X] [Bitmart](https://bitmart.com/)
|
||||||
|
- [X] [Bybit](https://bybit.com/)
|
||||||
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
||||||
- [X] [HTX](https://www.htx.com/) (Former Huobi)
|
- [X] [HTX](https://www.htx.com/)
|
||||||
|
- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX)
|
||||||
- [X] [Kraken](https://kraken.com/)
|
- [X] [Kraken](https://kraken.com/)
|
||||||
- [X] [OKX](https://okx.com/) (Former OKEX)
|
- [X] [OKX](https://okx.com/)
|
||||||
|
- [X] [MyOKX](https://okx.com/) (OKX EEA)
|
||||||
- [ ] [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)_
|
||||||
|
|
||||||
### Supported Futures Exchanges (experimental)
|
### Supported Futures Exchanges (experimental)
|
||||||
|
|
||||||
- [X] [Binance](https://www.binance.com/)
|
- [X] [Binance](https://www.binance.com/)
|
||||||
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
|
||||||
- [X] [OKX](https://okx.com/)
|
|
||||||
- [X] [Bybit](https://bybit.com/)
|
- [X] [Bybit](https://bybit.com/)
|
||||||
|
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
||||||
|
- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX)
|
||||||
|
- [X] [OKX](https://okx.com/)
|
||||||
|
|
||||||
Please make sure to read the [exchange specific notes](exchanges.md), as well as the [trading with leverage](leverage.md) documentation before diving in.
|
Please make sure to read the [exchange specific notes](exchanges.md), as well as the [trading with leverage](leverage.md) documentation before diving in.
|
||||||
|
|
||||||
@@ -84,7 +88,7 @@ To run this bot we recommend you a linux cloud instance with a minimum of:
|
|||||||
|
|
||||||
Alternatively
|
Alternatively
|
||||||
|
|
||||||
- Python 3.9+
|
- Python 3.10+
|
||||||
- pip (pip3)
|
- pip (pip3)
|
||||||
- git
|
- git
|
||||||
- TA-Lib
|
- TA-Lib
|
||||||
|
|||||||
+25
-18
@@ -24,7 +24,7 @@ The easiest way to install and run Freqtrade is to clone the bot Github reposito
|
|||||||
The `stable` branch contains the code of the last release (done usually once per month on an approximately one week old snapshot of the `develop` branch to prevent packaging bugs, so potentially it's more stable).
|
The `stable` branch contains the code of the last release (done usually once per month on an approximately one week old snapshot of the `develop` branch to prevent packaging bugs, so potentially it's more stable).
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
Python3.9 or higher and the corresponding `pip` are assumed to be available. The install-script will warn you and stop if that's not the case. `git` is also needed to clone the Freqtrade repository.
|
Python3.10 or higher and the corresponding `pip` are assumed to be available. The install-script will warn you and stop if that's not the case. `git` is also needed to clone the Freqtrade repository.
|
||||||
Also, python headers (`python<yourversion>-dev` / `python<yourversion>-devel`) must be available for the installation to complete successfully.
|
Also, python headers (`python<yourversion>-dev` / `python<yourversion>-devel`) must be available for the installation to complete successfully.
|
||||||
|
|
||||||
!!! Warning "Up-to-date clock"
|
!!! Warning "Up-to-date clock"
|
||||||
@@ -42,7 +42,7 @@ These requirements apply to both [Script Installation](#script-installation) and
|
|||||||
|
|
||||||
### Install guide
|
### Install guide
|
||||||
|
|
||||||
* [Python >= 3.9](http://docs.python-guide.org/en/latest/starting/installation/)
|
* [Python >= 3.10](http://docs.python-guide.org/en/latest/starting/installation/)
|
||||||
* [pip](https://pip.pypa.io/en/stable/installing/)
|
* [pip](https://pip.pypa.io/en/stable/installing/)
|
||||||
* [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
* [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
||||||
* [virtualenv](https://virtualenv.pypa.io/en/stable/installation.html) (Recommended)
|
* [virtualenv](https://virtualenv.pypa.io/en/stable/installation.html) (Recommended)
|
||||||
@@ -54,7 +54,7 @@ We've included/collected install instructions for Ubuntu, MacOS, and Windows. Th
|
|||||||
OS Specific steps are listed first, the 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.10 or higher and the corresponding pip are assumed to be available.
|
||||||
|
|
||||||
=== "Debian/Ubuntu"
|
=== "Debian/Ubuntu"
|
||||||
#### Install necessary dependencies
|
#### Install necessary dependencies
|
||||||
@@ -67,16 +67,28 @@ OS Specific steps are listed first, the common section below is necessary for al
|
|||||||
sudo apt install -y python3-pip python3-venv python3-dev python3-pandas git curl
|
sudo apt install -y python3-pip python3-venv python3-dev python3-pandas git curl
|
||||||
```
|
```
|
||||||
|
|
||||||
|
=== "MacOS"
|
||||||
|
#### Install necessary dependencies
|
||||||
|
|
||||||
|
Install [Homebrew](https://brew.sh/) if you don't have it already.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# install packages
|
||||||
|
brew install gettext libomp
|
||||||
|
```
|
||||||
|
!!! Note
|
||||||
|
The `setup.sh` script will install these dependencies for you - assuming brew is installed on your system.
|
||||||
|
|
||||||
=== "RaspberryPi/Raspbian"
|
=== "RaspberryPi/Raspbian"
|
||||||
The following assumes the latest [Raspbian Buster lite image](https://www.raspberrypi.org/downloads/raspbian/).
|
The following assumes the latest [Raspbian Buster lite image](https://www.raspberrypi.org/downloads/raspbian/).
|
||||||
This image comes with python3.9 preinstalled, making it easy to get freqtrade up and running.
|
This image comes with python3.11 preinstalled, making it easy to get freqtrade up and running.
|
||||||
|
|
||||||
Tested using a Raspberry Pi 3 with the Raspbian Buster lite image, all updates applied.
|
Tested using a Raspberry Pi 3 with the Raspbian Buster lite image, all updates applied.
|
||||||
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install python3-venv libatlas-base-dev cmake curl
|
sudo apt-get install python3-venv libatlas-base-dev cmake curl
|
||||||
# Use pywheels.org to speed up installation
|
# Use piwheels.org to speed up installation
|
||||||
sudo echo "[global]\nextra-index-url=https://www.piwheels.org/simple" > tee /etc/pip.conf
|
sudo echo "[global]\nextra-index-url=https://www.piwheels.org/simple" > tee /etc/pip.conf
|
||||||
|
|
||||||
git clone https://github.com/freqtrade/freqtrade.git
|
git clone https://github.com/freqtrade/freqtrade.git
|
||||||
@@ -150,9 +162,7 @@ Each time you open a new terminal, you must run `source .venv/bin/activate` to a
|
|||||||
source ./.venv/bin/activate
|
source ./.venv/bin/activate
|
||||||
```
|
```
|
||||||
|
|
||||||
### Congratulations
|
[You are now ready](#you-are-ready) to run the bot.
|
||||||
|
|
||||||
[You are ready](#you-are-ready), and run the bot
|
|
||||||
|
|
||||||
### Other options of /setup.sh script
|
### Other options of /setup.sh script
|
||||||
|
|
||||||
@@ -169,7 +179,7 @@ You can as well update, configure and reset the codebase of your bot with `./scr
|
|||||||
** --install **
|
** --install **
|
||||||
|
|
||||||
With this option, the script will install the bot and most dependencies:
|
With this option, the script will install the bot and most dependencies:
|
||||||
You will need to have git and python3.9+ installed beforehand for this to work.
|
You will need to have git and python3.10+ installed beforehand for this to work.
|
||||||
|
|
||||||
* Mandatory software as: `ta-lib`
|
* Mandatory software as: `ta-lib`
|
||||||
* Setup your virtualenv under `.venv/`
|
* Setup your virtualenv under `.venv/`
|
||||||
@@ -220,7 +230,7 @@ cd ..
|
|||||||
rm -rf ./ta-lib*
|
rm -rf ./ta-lib*
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Setup Python virtual environment (virtualenv)
|
### Setup Python virtual environment (virtualenv)
|
||||||
|
|
||||||
You will run freqtrade in separated `virtual environment`
|
You will run freqtrade in separated `virtual environment`
|
||||||
|
|
||||||
@@ -232,19 +242,18 @@ python3 -m venv .venv
|
|||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Install python dependencies
|
### Install python dependencies
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
python3 -m pip install -r requirements.txt
|
python3 -m pip install -r requirements.txt
|
||||||
|
# install freqtrade
|
||||||
python3 -m pip install -e .
|
python3 -m pip install -e .
|
||||||
```
|
```
|
||||||
|
|
||||||
### Congratulations
|
[You are now ready](#you-are-ready) to run the bot.
|
||||||
|
|
||||||
[You are ready](#you-are-ready), and run the bot
|
### (Optional) Post-installation Tasks
|
||||||
|
|
||||||
#### (Optional) Post-installation Tasks
|
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
If you run the bot on a server, you should consider using [Docker](docker_quickstart.md) or a terminal multiplexer like `screen` or [`tmux`](https://en.wikipedia.org/wiki/Tmux) to avoid that the bot is stopped on logout.
|
If you run the bot on a server, you should consider using [Docker](docker_quickstart.md) or a terminal multiplexer like `screen` or [`tmux`](https://en.wikipedia.org/wiki/Tmux) to avoid that the bot is stopped on logout.
|
||||||
@@ -333,9 +342,7 @@ cd build_helpers
|
|||||||
bash install_ta-lib.sh ${CONDA_PREFIX} nosudo
|
bash install_ta-lib.sh ${CONDA_PREFIX} nosudo
|
||||||
```
|
```
|
||||||
|
|
||||||
### Congratulations
|
[You are now ready](#you-are-ready) to run the bot.
|
||||||
|
|
||||||
[You are ready](#you-are-ready), and run the bot
|
|
||||||
|
|
||||||
### Important shortcuts
|
### Important shortcuts
|
||||||
|
|
||||||
|
|||||||
@@ -101,3 +101,4 @@ This could lead to a false-negative (the strategy will then be reported as non-b
|
|||||||
- `lookahead-analysis` has access to everything that backtesting has too.
|
- `lookahead-analysis` has access to everything that backtesting has too.
|
||||||
Please don't provoke any configs like enabling position stacking.
|
Please don't provoke any configs like enabling position stacking.
|
||||||
If you decide to do so, then make doubly sure that you won't ever run out of `max_open_trades` amount and neither leftover money in your wallet.
|
If you decide to do so, then make doubly sure that you won't ever run out of `max_open_trades` amount and neither leftover money in your wallet.
|
||||||
|
- In the results table, the `biased_indicators` column will falsely flag FreqAI target indicators defined in `set_freqai_targets()` as biased. These are not biased and can safely be ignored.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
markdown==3.6
|
markdown==3.7
|
||||||
mkdocs==1.6.0
|
mkdocs==1.6.1
|
||||||
mkdocs-material==9.5.29
|
mkdocs-material==9.5.49
|
||||||
mdx_truly_sane_lists==1.3
|
mdx_truly_sane_lists==1.3
|
||||||
pymdown-extensions==10.8.1
|
pymdown-extensions==10.12
|
||||||
jinja2==3.1.4
|
jinja2==3.1.5
|
||||||
|
mike==2.1.3
|
||||||
|
|||||||
+67
-53
@@ -88,8 +88,9 @@ Make sure that the following 2 lines are available in your docker-compose file:
|
|||||||
|
|
||||||
### Consuming the API
|
### Consuming the API
|
||||||
|
|
||||||
You can consume the API by using `freqtrade-client` (also available as `scripts/rest_client.py`).
|
We advise consuming the API by using the supported `freqtrade-client` package (also available as `scripts/rest_client.py`).
|
||||||
This command can be installed independent of the bot by using `pip install freqtrade-client`.
|
|
||||||
|
This command can be installed independent of any running freqtrade 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.
|
This module is designed to be lightweight, and only depends on the `requests` and `python-rapidjson` modules, skipping all heavy dependencies freqtrade otherwise needs.
|
||||||
|
|
||||||
@@ -144,57 +145,6 @@ This method will work for all arguments - check the "show" command for a list of
|
|||||||
|
|
||||||
For a full list of available commands, please refer to the list below.
|
For a full list of available commands, please refer to the list below.
|
||||||
|
|
||||||
### Available endpoints
|
|
||||||
|
|
||||||
| Command | Description |
|
|
||||||
|----------|-------------|
|
|
||||||
| `ping` | Simple command testing the API Readiness - requires no authentication.
|
|
||||||
| `start` | Starts the trader.
|
|
||||||
| `stop` | Stops the trader.
|
|
||||||
| `stopbuy` | Stops the trader from opening new trades. Gracefully closes open trades according to their rules.
|
|
||||||
| `reload_config` | Reloads the configuration file.
|
|
||||||
| `trades` | List last trades. Limited to 500 trades per call.
|
|
||||||
| `trade/<tradeid>` | Get specific trade.
|
|
||||||
| `trades/<tradeid>` | DELETE - Remove trade from the database. Tries to close open orders. Requires manual handling of this trade on the exchange.
|
|
||||||
| `trades/<tradeid>/open-order` | DELETE - Cancel open order for this trade.
|
|
||||||
| `trades/<tradeid>/reload` | GET - Reload a trade from the Exchange. Only works in live, and can potentially help recover a trade that was manually sold on the exchange.
|
|
||||||
| `show_config` | Shows part of the current configuration with relevant settings to operation.
|
|
||||||
| `logs` | Shows last log messages.
|
|
||||||
| `status` | Lists all open trades.
|
|
||||||
| `count` | Displays number of trades used and available.
|
|
||||||
| `entries [pair]` | Shows profit statistics for each enter tags for given pair (or all pairs if pair isn't given). Pair is optional.
|
|
||||||
| `exits [pair]` | Shows profit statistics for each 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.
|
|
||||||
| `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.
|
|
||||||
| `forceexit <trade_id> [order_type] [amount]` | Instantly exits the given trade (ignoring `minimum_roi`), using the given order type ("market" or "limit", uses your config setting if not specified), and the chosen amount (full sell if not specified).
|
|
||||||
| `forceexit all` | Instantly exits all open trades (Ignoring `minimum_roi`).
|
|
||||||
| `forceenter <pair> [rate]` | Instantly enters the given pair. Rate is optional. (`force_entry_enable` must be set to True)
|
|
||||||
| `forceenter <pair> <side> [rate]` | Instantly longs or shorts the given pair. Rate is optional. (`force_entry_enable` must be set to True)
|
|
||||||
| `performance` | Show performance of each finished trade grouped by pair.
|
|
||||||
| `balance` | Show account balance per currency.
|
|
||||||
| `daily <n>` | Shows profit or loss per day, over the last n days (n defaults to 7).
|
|
||||||
| `weekly <n>` | Shows profit or loss per week, over the last n days (n defaults to 4).
|
|
||||||
| `monthly <n>` | Shows profit or loss per month, over the last n days (n defaults to 3).
|
|
||||||
| `stats` | Display a summary of profit / loss reasons as well as average holding times.
|
|
||||||
| `whitelist` | Show the current whitelist.
|
|
||||||
| `blacklist [pair]` | Show the current blacklist, or adds a pair to the blacklist.
|
|
||||||
| `edge` | Show validated pairs by Edge if it is enabled.
|
|
||||||
| `pair_candles` | Returns dataframe for a pair / timeframe combination while the bot is running. **Alpha**
|
|
||||||
| `pair_history` | Returns an analyzed dataframe for a given timerange, analyzed by a given strategy. **Alpha**
|
|
||||||
| `plot_config` | Get plot config from the strategy (or nothing if not configured). **Alpha**
|
|
||||||
| `strategies` | List strategies in strategy directory. **Alpha**
|
|
||||||
| `strategy <strategy>` | Get specific Strategy content. **Alpha**
|
|
||||||
| `available_pairs` | List available backtest data. **Alpha**
|
|
||||||
| `version` | Show version.
|
|
||||||
| `sysinfo` | Show information about the system load.
|
|
||||||
| `health` | Show bot health (last bot loop).
|
|
||||||
|
|
||||||
!!! Warning "Alpha status"
|
|
||||||
Endpoints labeled with *Alpha status* above may change at any time without notice.
|
|
||||||
|
|
||||||
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
|
||||||
@@ -266,6 +216,14 @@ forceexit
|
|||||||
health
|
health
|
||||||
Provides a quick health check of the running bot.
|
Provides a quick health check of the running bot.
|
||||||
|
|
||||||
|
lock_add
|
||||||
|
Manually lock a specific pair
|
||||||
|
|
||||||
|
:param pair: Pair to lock
|
||||||
|
:param until: Lock until this date (format "2024-03-30 16:00:00Z")
|
||||||
|
:param side: Side to lock (long, short, *)
|
||||||
|
:param reason: Reason for the lock
|
||||||
|
|
||||||
locks
|
locks
|
||||||
Return current locks
|
Return current locks
|
||||||
|
|
||||||
@@ -353,6 +311,62 @@ whitelist
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Available endpoints
|
||||||
|
|
||||||
|
If you wish to call the REST API manually via another route, e.g. directly via `curl`, the table below shows the relevant URL endpoints and parameters.
|
||||||
|
All endpoints in the below table need to be prefixed with the base URL of the API, e.g. `http://127.0.0.1:8080/api/v1/` - so the command becomes `http://127.0.0.1:8080/api/v1/<command>`.
|
||||||
|
|
||||||
|
| Endpoint | Method | Description / Parameters |
|
||||||
|
|-----------|--------|--------------------------|
|
||||||
|
| `/ping` | GET | Simple command testing the API Readiness - requires no authentication.
|
||||||
|
| `/start` | POST | Starts the trader.
|
||||||
|
| `/stop` | POST | Stops the trader.
|
||||||
|
| `/stopbuy` | POST | Stops the trader from opening new trades. Gracefully closes open trades according to their rules.
|
||||||
|
| `/reload_config` | POST | Reloads the configuration file.
|
||||||
|
| `/trades` | GET | List last trades. Limited to 500 trades per call.
|
||||||
|
| `/trade/<tradeid>` | GET | Get specific trade.<br/>*Params:*<br/>- `tradeid` (`int`)
|
||||||
|
| `/trades/<tradeid>` | DELETE | Remove trade from the database. Tries to close open orders. Requires manual handling of this trade on the exchange.<br/>*Params:*<br/>- `tradeid` (`int`)
|
||||||
|
| `/trades/<tradeid>/open-order` | DELETE | Cancel open order for this trade.<br/>*Params:*<br/>- `tradeid` (`int`)
|
||||||
|
| `/trades/<tradeid>/reload` | POST | Reload a trade from the Exchange. Only works in live, and can potentially help recover a trade that was manually sold on the exchange.<br/>*Params:*<br/>- `tradeid` (`int`)
|
||||||
|
| `/show_config` | GET | Shows part of the current configuration with relevant settings to operation.
|
||||||
|
| `/logs` | GET | Shows last log messages.
|
||||||
|
| `/status` | GET | Lists all open trades.
|
||||||
|
| `/count` | GET | Displays number of trades used and available.
|
||||||
|
| `/entries` | GET | Shows profit statistics for each enter tags for given pair (or all pairs if pair isn't given). Pair is optional.<br/>*Params:*<br/>- `pair` (`str`)
|
||||||
|
| `/exits` | GET | Shows profit statistics for each exit reasons for given pair (or all pairs if pair isn't given). Pair is optional.<br/>*Params:*<br/>- `pair` (`str`)
|
||||||
|
| `/mix_tags` | GET | 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.<br/>*Params:*<br/>- `pair` (`str`)
|
||||||
|
| `/locks` | GET | Displays currently locked pairs.
|
||||||
|
| `/locks` | POST | Locks a pair until "until". (Until will be rounded up to the nearest timeframe). Side is optional and is either `long` or `short` (default is `long`). Reason is optional.<br/>*Params:*<br/>- `<pair>` (`str`)<br/>- `<until>` (`datetime`)<br/>- `[side]` (`str`)<br/>- `[reason]` (`str`)
|
||||||
|
| `/locks/<lockid>` | DELETE | Deletes (disables) the lock by id.<br/>*Params:*<br/>- `lockid` (`int`)
|
||||||
|
| `/profit` | GET | Display a summary of your profit/loss from close trades and some stats about your performance.
|
||||||
|
| `/forceexit` | POST | Instantly exits the given trade (ignoring `minimum_roi`), using the given order type ("market" or "limit", uses your config setting if not specified), and the chosen amount (full sell if not specified). If `all` is supplied as the `tradeid`, then all currently open trades will be forced to exit.<br/>*Params:*<br/>- `<tradeid>` (`int` or `str`)<br/>- `<ordertype>` (`str`)<br/>- `[amount]` (`float`)
|
||||||
|
| `/forceenter` | POST | Instantly enters the given pair. Side is optional and is either `long` or `short` (default is `long`). Rate is optional. (`force_entry_enable` must be set to True)<br/>*Params:*<br/>- `<pair>` (`str`)<br/>- `<side>` (`str`)<br/>- `[rate]` (`float`)
|
||||||
|
| `/performance` | GET | Show performance of each finished trade grouped by pair.
|
||||||
|
| `/balance` | GET | Show account balance per currency.
|
||||||
|
| `/daily` | GET | Shows profit or loss per day, over the last n days (n defaults to 7).<br/>*Params:*<br/>- `<n>` (`int`)
|
||||||
|
| `/weekly` | GET | Shows profit or loss per week, over the last n days (n defaults to 4).<br/>*Params:*<br/>- `<n>` (`int`)
|
||||||
|
| `/monthly` | GET | Shows profit or loss per month, over the last n days (n defaults to 3).<br/>*Params:*<br/>- `<n>` (`int`)
|
||||||
|
| `/stats` | GET | Display a summary of profit / loss reasons as well as average holding times.
|
||||||
|
| `/whitelist` | GET | Show the current whitelist.
|
||||||
|
| `/blacklist` | GET | Show the current blacklist.
|
||||||
|
| `/blacklist` | POST | Adds the specified pair to the blacklist.<br/>*Params:*<br/>- `pair` (`str`)
|
||||||
|
| `/blacklist` | DELETE | Deletes the specified list of pairs from the blacklist.<br/>*Params:*<br/>- `[pair,pair]` (`list[str]`)
|
||||||
|
| `/edge` | GET | Show validated pairs by Edge if it is enabled.
|
||||||
|
| `/pair_candles` | GET | Returns dataframe for a pair / timeframe combination while the bot is running. **Alpha**
|
||||||
|
| `/pair_candles` | POST | Returns dataframe for a pair / timeframe combination while the bot is running, filtered by a provided list of columns to return. **Alpha**<br/>*Params:*<br/>- `<column_list>` (`list[str]`)
|
||||||
|
| `/pair_history` | GET | Returns an analyzed dataframe for a given timerange, analyzed by a given strategy. **Alpha**
|
||||||
|
| `/pair_history` | POST | Returns an analyzed dataframe for a given timerange, analyzed by a given strategy, filtered by a provided list of columns to return. **Alpha**<br/>*Params:*<br/>- `<column_list>` (`list[str]`)
|
||||||
|
| `/plot_config` | GET | Get plot config from the strategy (or nothing if not configured). **Alpha**
|
||||||
|
| `/strategies` | GET | List strategies in strategy directory. **Alpha**
|
||||||
|
| `/strategy/<strategy>` | GET | Get specific Strategy content by strategy class name. **Alpha**<br/>*Params:*<br/>- `<strategy>` (`str`)
|
||||||
|
| `/available_pairs` | GET | List available backtest data. **Alpha**
|
||||||
|
| `/version` | GET | Show version.
|
||||||
|
| `/sysinfo` | GET | Show information about the system load.
|
||||||
|
| `/health` | GET | Show bot health (last bot loop).
|
||||||
|
|
||||||
|
!!! Warning "Alpha status"
|
||||||
|
Endpoints labeled with *Alpha status* above may change at any time without notice.
|
||||||
|
|
||||||
### Message WebSocket
|
### Message WebSocket
|
||||||
|
|
||||||
The API Server includes a websocket endpoint for subscribing to RPC messages from the freqtrade Bot.
|
The API Server includes a websocket endpoint for subscribing to RPC messages from the freqtrade Bot.
|
||||||
|
|||||||
+3
-2
@@ -30,12 +30,13 @@ The Order-type will be ignored if only one mode is available.
|
|||||||
|----------|-------------|
|
|----------|-------------|
|
||||||
| Binance | limit |
|
| Binance | limit |
|
||||||
| Binance Futures | market, limit |
|
| Binance Futures | market, limit |
|
||||||
| Bingx | market, limit |
|
| Bingx | market, limit |
|
||||||
| HTX (former Huobi) | limit |
|
| HTX | limit |
|
||||||
| kraken | market, limit |
|
| kraken | market, limit |
|
||||||
| Gate | limit |
|
| Gate | limit |
|
||||||
| Okx | limit |
|
| Okx | limit |
|
||||||
| Kucoin | stop-limit, stop-market|
|
| Kucoin | stop-limit, stop-market|
|
||||||
|
| Hyperliquid (futures only) | limit |
|
||||||
|
|
||||||
!!! Note "Tight stoploss"
|
!!! Note "Tight stoploss"
|
||||||
<ins>Do not set too low/tight stoploss value when using stop loss on exchange!</ins>
|
<ins>Do not set too low/tight stoploss value when using stop loss on exchange!</ins>
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
# Freqtrade Strategies 101: A Quick Start for Strategy Development
|
||||||
|
|
||||||
|
For the purposes of this quick start, we are assuming you are familiar with the basics of trading, and have read the
|
||||||
|
[Freqtrade basics](bot-basics.md) page.
|
||||||
|
|
||||||
|
## Required Knowledge
|
||||||
|
|
||||||
|
A strategy in Freqtrade is a Python class that defines the logic for buying and selling cryptocurrency `assets`.
|
||||||
|
|
||||||
|
Assets are defined as `pairs`, which represent the `coin` and the `stake`. The coin is the asset you are trading using another currency as the stake.
|
||||||
|
|
||||||
|
Data is supplied by the exchange in the form of `candles`, which are made up of a six values: `date`, `open`, `high`, `low`, `close` and `volume`.
|
||||||
|
|
||||||
|
`Technical analysis` functions analyse the candle data using various computational and statistical formulae, and produce secondary values called `indicators`.
|
||||||
|
|
||||||
|
Indicators are analysed on the asset pair candles to generate `signals`.
|
||||||
|
|
||||||
|
Signals are turned into `orders` on a cryptocurrency `exchange`, i.e. `trades`.
|
||||||
|
|
||||||
|
We use the terms `entry` and `exit` instead of `buying` and `selling` because Freqtrade supports both `long` and `short` trades.
|
||||||
|
|
||||||
|
- **long**: You buy the coin based on a stake, e.g. buying the coin BTC using USDT as your stake, and you make a profit by selling the coin at a higher rate than you paid for. In long trades, profits are made by the coin value going up versus the stake.
|
||||||
|
- **short**: You borrow capital from the exchange in the form of the coin, and you pay back the stake value of the coin later. In short trades profits are made by the coin value going down versus the stake (you pay the loan off at a lower rate).
|
||||||
|
|
||||||
|
Whilst Freqtrade supports spot and futures markets for certain exchanges, for simplicity we will focus on spot (long) trades only.
|
||||||
|
|
||||||
|
## Structure of a Basic Strategy
|
||||||
|
|
||||||
|
### Main dataframe
|
||||||
|
|
||||||
|
Freqtrade strategies use a tabular data structure with rows and columns known as a `dataframe` to generate signals to enter and exit trades.
|
||||||
|
|
||||||
|
Each pair in your configured pairlist has its own dataframe. Dataframes are indexed by the `date` column, e.g. `2024-06-31 12:00`.
|
||||||
|
|
||||||
|
The next 5 columns represent the `open`, `high`, `low`, `close` and `volume` (OHLCV) data.
|
||||||
|
|
||||||
|
### Populate indicator values
|
||||||
|
|
||||||
|
The `populate_indicators` function adds columns to the dataframe that represent the technical analysis indicator values.
|
||||||
|
|
||||||
|
Examples of common indicators include Relative Strength Index, Bollinger Bands, Money Flow Index, Moving Average, and Average True Range.
|
||||||
|
|
||||||
|
Columns are added to the dataframe by calling technical analysis functions, e.g. ta-lib's RSI function `ta.RSI()`, and assigning them to a column name, e.g. `rsi`
|
||||||
|
|
||||||
|
```python
|
||||||
|
dataframe['rsi'] = ta.RSI(dataframe)
|
||||||
|
```
|
||||||
|
|
||||||
|
??? Hint "Technical Analysis libraries"
|
||||||
|
Different libraries work in different ways to generate indicator values. Please check the documentation of each library to understand
|
||||||
|
how to integrate it into your strategy. You can also check the [Freqtrade example strategies](https://github.com/freqtrade/freqtrade-strategies) to give you ideas.
|
||||||
|
|
||||||
|
### Populate entry signals
|
||||||
|
|
||||||
|
The `populate_entry_trend` function defines conditions for an entry signal.
|
||||||
|
|
||||||
|
The dataframe column `enter_long` is added to the dataframe, and when a value of `1` is in this column, Freqtrade sees an entry signal.
|
||||||
|
|
||||||
|
??? Hint "Shorting"
|
||||||
|
To enter short trades, use the `enter_short` column.
|
||||||
|
|
||||||
|
### Populate exit signals
|
||||||
|
|
||||||
|
The `populate_exit_trend` function defines conditions for an exit signal.
|
||||||
|
|
||||||
|
The dataframe column `exit_long` is added to the dataframe, and when a value of `1` is in this column, Freqtrade sees an exit signal.
|
||||||
|
|
||||||
|
??? Hint "Shorting"
|
||||||
|
To exit short trades, use the `exit_short` column.
|
||||||
|
|
||||||
|
## A simple strategy
|
||||||
|
|
||||||
|
Here is a minimal example of a Freqtrade strategy:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from freqtrade.strategy import IStrategy
|
||||||
|
from pandas import DataFrame
|
||||||
|
import talib.abstract as ta
|
||||||
|
|
||||||
|
class MyStrategy(IStrategy):
|
||||||
|
|
||||||
|
# set the initial stoploss to -10%
|
||||||
|
stoploss = -0.10
|
||||||
|
|
||||||
|
# exit profitable positions at any time when the profit is greater than 1%
|
||||||
|
minimal_roi = {"0": 0.01}
|
||||||
|
|
||||||
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
# generate values for technical analysis indicators
|
||||||
|
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
# generate entry signals based on indicator values
|
||||||
|
dataframe.loc[
|
||||||
|
(dataframe['rsi'] < 30),
|
||||||
|
'enter_long'] = 1
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
# generate exit signals based on indicator values
|
||||||
|
dataframe.loc[
|
||||||
|
(dataframe['rsi'] > 70),
|
||||||
|
'exit_long'] = 1
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
```
|
||||||
|
|
||||||
|
## Making trades
|
||||||
|
|
||||||
|
When a signal is found (a `1` in an entry or exit column), Freqtrade will attempt to make an order, i.e. a `trade` or `position`.
|
||||||
|
|
||||||
|
Each new trade position takes up a `slot`. Slots represent the maximum number of concurrent new trades that can be opened.
|
||||||
|
|
||||||
|
The number of slots is defined by the `max_open_trades` [configuration](configuration.md) option.
|
||||||
|
|
||||||
|
However, there can be a range of scenarios where generating a signal does not always create a trade order. These include:
|
||||||
|
|
||||||
|
- not enough remaining stake to buy an asset, or funds in your wallet to sell an asset (including any fees)
|
||||||
|
- not enough remaining free slots for a new trade to be opened (the number of positions you have open equals the `max_open_trades` option)
|
||||||
|
- there is already an open trade for a pair (Freqtrade cannot stack positions - however it can [adjust existing positions](strategy-callbacks.md#adjust-trade-position))
|
||||||
|
- if an entry and exit signal is present on the same candle, they are considered as [colliding](strategy-customization.md#colliding-signals), and no order will be raised
|
||||||
|
- the strategy actively rejects the trade order due to logic you specify by using one of the relevant [entry](strategy-callbacks.md#trade-entry-buy-order-confirmation) or [exit](strategy-callbacks.md#trade-exit-sell-order-confirmation) callbacks
|
||||||
|
|
||||||
|
Read through the [strategy customization](strategy-customization.md) documentation for more details.
|
||||||
|
|
||||||
|
## Backtesting and forward testing
|
||||||
|
|
||||||
|
Strategy development can be a long and frustrating process, as turning our human "gut instincts" into a working computer-controlled
|
||||||
|
("algo") strategy is not always straightforward.
|
||||||
|
|
||||||
|
Therefore a strategy should be tested to verify that it is going to work as intended.
|
||||||
|
|
||||||
|
Freqtrade has two testing modes:
|
||||||
|
|
||||||
|
- **backtesting**: using historical data that you [download from an exchange](data-download.md), backtesting is a quick way to assess performance of a strategy. However, it can be very easy to distort results so a strategy will look a lot more profitable than it really is. Check the [backtesting documentation](backtesting.md) for more information.
|
||||||
|
- **dry run**: often referred to as _forward testing_, dry runs use real time data from the exchange. However, any signals that would result in trades are tracked as normal by Freqtrade, but do not have any trades opened on the exchange itself. Forward testing runs in real time, so whilst it takes longer to get results it is a much more reliable indicator of **potential** performance than backtesting.
|
||||||
|
|
||||||
|
Dry runs are enabled by setting `dry_run` to true in your [configuration](configuration.md#using-dry-run-mode).
|
||||||
|
|
||||||
|
!!! Warning "Backtests can be very inaccurate"
|
||||||
|
There are many reasons why backtest results may not match reality. Please check the [backtesting assumptions](backtesting.md#assumptions-made-by-backtesting) and [common strategy mistakes](strategy-customization.md#common-mistakes-when-developing-strategies) documentation.
|
||||||
|
Some websites that list and rank Freqtrade strategies show impressive backtest results. Do not assume these results are achieveable or realistic.
|
||||||
|
|
||||||
|
??? Hint "Useful commands"
|
||||||
|
Freqtrade includes two useful commands to check for basic flaws in strategies: [lookahead-analysis](lookahead-analysis.md) and [recursive-analysis](recursive-analysis.md).
|
||||||
|
|
||||||
|
### Assessing backtesting and dry run results
|
||||||
|
|
||||||
|
Always dry run your strategy after backtesting it to see if backtesting and dry run results are sufficiently similar.
|
||||||
|
|
||||||
|
If there is any significant difference, verify that your entry and exit signals are consistent and appear on the same candles between the two modes. However, there will always be differences between dry runs and backtests:
|
||||||
|
|
||||||
|
- Backtesting assumes all orders fill. In dry runs this might not be the case if using limit orders or there is no volume on the exchange.
|
||||||
|
- Following an entry signal on candle close, backtesting assumes trades enter at the next candle's open price (unless you have custom pricing callbacks in your strategy). In dry runs, there is often a delay between signals and trades opening.
|
||||||
|
This is because when new candles come in on your main timeframe, e.g. every 5 minutes, it takes time for Freqtrade to analyse all pair dataframes. Therefore, Freqtrade will attempt to open trades a few seconds (ideally a small a delay as possible)
|
||||||
|
after candle open.
|
||||||
|
- As entry rates in dry runs might not match backtesting, this means profit calculations will also differ. Therefore, it is normal if ROI, stoploss, trailing stoploss and callback exits are not identical.
|
||||||
|
- The more computational "lag" you have between new candles coming in and your signals being raised and trades being opened will result in greater price unpredictability. Make sure your computer is powerful enough to process the data for the number
|
||||||
|
of pairs you have in your pairlist within a reasonable time. Freqtrade will warn you in the logs if there are significant data processing delays.
|
||||||
|
|
||||||
|
## Controlling or monitoring a running bot
|
||||||
|
|
||||||
|
Once your bot is running in dry or live mode, Freqtrade has five mechanisms to control or monitor a running bot:
|
||||||
|
|
||||||
|
- **[FreqUI](freq-ui.md)**: The easiest to get started with, FreqUI is a web interface to see and control current activity of your bot.
|
||||||
|
- **[Telegram](telegram-usage.md)**: On mobile devices, Telegram integration is available to get alerts about your bot activity and to control certain aspects.
|
||||||
|
- **[FTUI](https://github.com/freqtrade/ftui)**: FTUI is a terminal (command line) interface to Freqtrade, and allows monitoring of a running bot only.
|
||||||
|
- **[REST API](rest-api.md)**: The REST API allows programmers to develop their own tools to interact with a Freqtrade bot.
|
||||||
|
- **[Webhooks](webhook-config.md)**: Freqtrade can send information to other services, e.g. discord, by webhooks.
|
||||||
|
|
||||||
|
### Logs
|
||||||
|
|
||||||
|
Freqtrade generates extensive debugging logs to help you understand what's happening. Please familiarise yourself with the information and error messages you might see in your bot logs.
|
||||||
|
|
||||||
|
## Final Thoughts
|
||||||
|
|
||||||
|
Algo trading is difficult, and most public strategies are not good performers due to the time and effort to make a strategy work profitably in multiple scenarios.
|
||||||
|
|
||||||
|
Therefore, taking public strategies and using backtests as a way to assess performance is often problematic. However, Freqtrade provides useful ways to help you make decisions and do your due diligence.
|
||||||
|
|
||||||
|
There are many different ways to achieve profitability, and there is no one single tip, trick or config option that will fix a poorly performing strategy.
|
||||||
|
|
||||||
|
Freqtrade is an open source platform with a large and helpful community - make sure to visit our [discord channel](https://discord.gg/p7nuUNVfP7) to discuss your strategy with others!
|
||||||
|
|
||||||
|
As always, only invest what you are willing to lose.
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
Developing a strategy in Freqtrade involves defining entry and exit signals based on technical indicators. By following the structure and methods outlined above, you can create and test your own trading strategies.
|
||||||
|
|
||||||
|
Common questions and answers are available on our [FAQ](faq.md).
|
||||||
|
|
||||||
|
To continue, refer to the more in-depth [Freqtrade strategy customization documentation](strategy-customization.md).
|
||||||
@@ -38,9 +38,9 @@ class AwesomeStrategy(IStrategy):
|
|||||||
trade.set_custom_data(key='entry_type', value=trade_entry_type)
|
trade.set_custom_data(key='entry_type', value=trade_entry_type)
|
||||||
return super().bot_loop_start(**kwargs)
|
return super().bot_loop_start(**kwargs)
|
||||||
|
|
||||||
def adjust_entry_price(self, trade: Trade, order: Optional[Order], pair: str,
|
def adjust_entry_price(self, trade: Trade, order: Order | None, pair: str,
|
||||||
current_time: datetime, proposed_rate: float, current_order_rate: float,
|
current_time: datetime, proposed_rate: float, current_order_rate: float,
|
||||||
entry_tag: Optional[str], side: str, **kwargs) -> float:
|
entry_tag: str | None, side: str, **kwargs) -> float:
|
||||||
# Limit orders to use and follow SMA200 as price target for the first 10 minutes since entry trigger for BTC/USDT pair.
|
# Limit orders to use and follow SMA200 as price target for the first 10 minutes since entry trigger for BTC/USDT pair.
|
||||||
if (
|
if (
|
||||||
pair == 'BTC/USDT'
|
pair == 'BTC/USDT'
|
||||||
|
|||||||
+133
-124
@@ -24,6 +24,8 @@ Currently available callbacks:
|
|||||||
!!! 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)
|
||||||
|
|
||||||
|
--8<-- "includes/strategy-imports.md"
|
||||||
|
|
||||||
## Bot start
|
## Bot start
|
||||||
|
|
||||||
A simple callback which is called once when the strategy is loaded.
|
A simple callback which is called once when the strategy is loaded.
|
||||||
@@ -41,10 +43,10 @@ class AwesomeStrategy(IStrategy):
|
|||||||
Called only once after bot instantiation.
|
Called only once after bot instantiation.
|
||||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
"""
|
"""
|
||||||
if self.config['runmode'].value in ('live', 'dry_run'):
|
if self.config["runmode"].value in ("live", "dry_run"):
|
||||||
# Assign this to the class by using self.*
|
# Assign this to the class by using self.*
|
||||||
# can then be used by populate_* methods
|
# can then be used by populate_* methods
|
||||||
self.custom_remote_data = requests.get('https://some_remote_source.example.com')
|
self.custom_remote_data = requests.get("https://some_remote_source.example.com")
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -57,6 +59,7 @@ seconds, unless configured differently) or once per candle in backtest/hyperopt
|
|||||||
This can be used to perform calculations which are pair independent (apply to all pairs), loading of external data, etc.
|
This can be used to perform calculations which are pair independent (apply to all pairs), loading of external data, etc.
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
|
# Default imports
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
@@ -71,10 +74,10 @@ class AwesomeStrategy(IStrategy):
|
|||||||
:param current_time: datetime object, containing the current datetime
|
: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.
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
"""
|
"""
|
||||||
if self.config['runmode'].value in ('live', 'dry_run'):
|
if self.config["runmode"].value in ("live", "dry_run"):
|
||||||
# Assign this to the class by using self.*
|
# Assign this to the class by using self.*
|
||||||
# can then be used by populate_* methods
|
# can then be used by populate_* methods
|
||||||
self.remote_data = requests.get('https://some_remote_source.example.com')
|
self.remote_data = requests.get("https://some_remote_source.example.com")
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -83,22 +86,24 @@ class AwesomeStrategy(IStrategy):
|
|||||||
Called before entering a trade, makes it possible to manage your position size when placing a new trade.
|
Called before entering a trade, makes it possible to manage your position size when placing a new trade.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
# Default imports
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
|
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
|
||||||
proposed_stake: float, min_stake: Optional[float], max_stake: float,
|
proposed_stake: float, min_stake: float | None, max_stake: float,
|
||||||
leverage: float, entry_tag: Optional[str], side: str,
|
leverage: float, entry_tag: str | None, side: str,
|
||||||
**kwargs) -> float:
|
**kwargs) -> float:
|
||||||
|
|
||||||
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
|
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
|
||||||
current_candle = dataframe.iloc[-1].squeeze()
|
current_candle = dataframe.iloc[-1].squeeze()
|
||||||
|
|
||||||
if current_candle['fastk_rsi_1h'] > current_candle['fastd_rsi_1h']:
|
if current_candle["fastk_rsi_1h"] > current_candle["fastd_rsi_1h"]:
|
||||||
if self.config['stake_amount'] == 'unlimited':
|
if self.config["stake_amount"] == "unlimited":
|
||||||
# Use entire available wallet during favorable conditions when in compounding mode.
|
# Use entire available wallet during favorable conditions when in compounding mode.
|
||||||
return max_stake
|
return max_stake
|
||||||
else:
|
else:
|
||||||
# Compound profits during favorable conditions instead of using a static stake.
|
# Compound profits during favorable conditions instead of using a static stake.
|
||||||
return self.wallets.get_total_stake_amount() / self.config['max_open_trades']
|
return self.wallets.get_total_stake_amount() / self.config["max_open_trades"]
|
||||||
|
|
||||||
# Use default stake amount.
|
# Use default stake amount.
|
||||||
return proposed_stake
|
return proposed_stake
|
||||||
@@ -129,25 +134,27 @@ Using `custom_exit()` signals in place of stoploss though *is not recommended*.
|
|||||||
An example of how we can use different indicators depending on the current profit and also exit trades that were open longer than one day:
|
An example of how we can use different indicators depending on the current profit and also exit trades that were open longer than one day:
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
|
# Default imports
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
|
def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
|
||||||
current_profit: float, **kwargs):
|
current_profit: float, **kwargs):
|
||||||
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
||||||
last_candle = dataframe.iloc[-1].squeeze()
|
last_candle = dataframe.iloc[-1].squeeze()
|
||||||
|
|
||||||
# Above 20% profit, sell when rsi < 80
|
# Above 20% profit, sell when rsi < 80
|
||||||
if current_profit > 0.2:
|
if current_profit > 0.2:
|
||||||
if last_candle['rsi'] < 80:
|
if last_candle["rsi"] < 80:
|
||||||
return 'rsi_below_80'
|
return "rsi_below_80"
|
||||||
|
|
||||||
# Between 2% and 10%, sell if EMA-long above EMA-short
|
# Between 2% and 10%, sell if EMA-long above EMA-short
|
||||||
if 0.02 < current_profit < 0.1:
|
if 0.02 < current_profit < 0.1:
|
||||||
if last_candle['emalong'] > last_candle['emashort']:
|
if last_candle["emalong"] > last_candle["emashort"]:
|
||||||
return 'ema_long_below_80'
|
return "ema_long_below_80"
|
||||||
|
|
||||||
# Sell any positions at a loss if they are held for more than one day.
|
# Sell any positions at a loss if they are held for more than one day.
|
||||||
if current_profit < 0.0 and (current_time - trade.open_date_utc).days >= 1:
|
if current_profit < 0.0 and (current_time - trade.open_date_utc).days >= 1:
|
||||||
return 'unclog'
|
return "unclog"
|
||||||
```
|
```
|
||||||
|
|
||||||
See [Dataframe access](strategy-advanced.md#dataframe-access) for more information about dataframe use in strategy callbacks.
|
See [Dataframe access](strategy-advanced.md#dataframe-access) for more information about dataframe use in strategy callbacks.
|
||||||
@@ -158,7 +165,8 @@ Called for open trade every iteration (roughly every 5 seconds) until a trade is
|
|||||||
|
|
||||||
The usage of the custom stoploss method must be enabled by setting `use_custom_stoploss=True` on the strategy object.
|
The usage of the custom stoploss method must be enabled by setting `use_custom_stoploss=True` on the strategy object.
|
||||||
|
|
||||||
The stoploss price can only ever move upwards - if the stoploss value returned from `custom_stoploss` would result in a lower stoploss price than was previously set, it will be ignored. The traditional `stoploss` value serves as an absolute lower level and will be instated as the initial stoploss (before this method is called for the first time for a trade), and is still mandatory.
|
The stoploss price can only ever move upwards - if the stoploss value returned from `custom_stoploss` would result in a lower stoploss price than was previously set, it will be ignored. The traditional `stoploss` value serves as an absolute lower level and will be instated as the initial stoploss (before this method is called for the first time for a trade), and is still mandatory.
|
||||||
|
As custom stoploss acts as regular, changing stoploss, it will behave similar to `trailing_stop` - and trades exiting due to this will have the exit_reason of `"trailing_stop_loss"`.
|
||||||
|
|
||||||
The method must return a stoploss value (float / number) as a percentage of the current price.
|
The method must return a stoploss value (float / number) as a percentage of the current price.
|
||||||
E.g. If the `current_rate` is 200 USD, then returning `0.02` will set the stoploss price 2% lower, at 196 USD.
|
E.g. If the `current_rate` is 200 USD, then returning `0.02` will set the stoploss price 2% lower, at 196 USD.
|
||||||
@@ -168,7 +176,6 @@ The absolute value of the return value is used (the sign is ignored), so returni
|
|||||||
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.
|
||||||
`NaN` and `inf` values are considered invalid and will be ignored (identical to `None`).
|
`NaN` and `inf` values are considered invalid and will be ignored (identical to `None`).
|
||||||
|
|
||||||
|
|
||||||
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)).
|
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"
|
||||||
@@ -196,9 +203,7 @@ Of course, many more things are possible, and all examples can be combined at wi
|
|||||||
To simulate a regular trailing stoploss of 4% (trailing 4% behind the maximum reached price) you would use the following very simple method:
|
To simulate a regular trailing stoploss of 4% (trailing 4% behind the maximum reached price) you would use the following very simple method:
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
# additional imports required
|
# Default imports
|
||||||
from datetime import datetime
|
|
||||||
from freqtrade.persistence import Trade
|
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
@@ -206,9 +211,9 @@ class AwesomeStrategy(IStrategy):
|
|||||||
|
|
||||||
use_custom_stoploss = True
|
use_custom_stoploss = True
|
||||||
|
|
||||||
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
|
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
|
||||||
current_rate: float, current_profit: float, after_fill: bool,
|
current_rate: float, current_profit: float, after_fill: bool,
|
||||||
**kwargs) -> Optional[float]:
|
**kwargs) -> float | None:
|
||||||
"""
|
"""
|
||||||
Custom stoploss logic, returning the new distance relative to current_rate (as ratio).
|
Custom stoploss logic, returning the new distance relative to current_rate (as ratio).
|
||||||
e.g. returning -0.05 would create a stoploss 5% below current_rate.
|
e.g. returning -0.05 would create a stoploss 5% below current_rate.
|
||||||
@@ -236,8 +241,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
Use the initial stoploss for the first 60 minutes, after this change to 10% trailing stoploss, and after 2 hours (120 minutes) we use a 5% trailing stoploss.
|
Use the initial stoploss for the first 60 minutes, after this change to 10% trailing stoploss, and after 2 hours (120 minutes) we use a 5% trailing stoploss.
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
from datetime import datetime, timedelta
|
# Default imports
|
||||||
from freqtrade.persistence import Trade
|
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
@@ -245,9 +249,9 @@ class AwesomeStrategy(IStrategy):
|
|||||||
|
|
||||||
use_custom_stoploss = True
|
use_custom_stoploss = True
|
||||||
|
|
||||||
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
|
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
|
||||||
current_rate: float, current_profit: float, after_fill: bool,
|
current_rate: float, current_profit: float, after_fill: bool,
|
||||||
**kwargs) -> Optional[float]:
|
**kwargs) -> float | None:
|
||||||
|
|
||||||
# Make sure you have the longest interval first - these conditions are evaluated from top to bottom.
|
# Make sure you have the longest interval first - these conditions are evaluated from top to bottom.
|
||||||
if current_time - timedelta(minutes=120) > trade.open_date_utc:
|
if current_time - timedelta(minutes=120) > trade.open_date_utc:
|
||||||
@@ -263,8 +267,7 @@ Use the initial stoploss for the first 60 minutes, after this change to 10% trai
|
|||||||
If an additional order fills, set stoploss to -10% below the new `open_rate` ([Averaged across all entries](#position-adjust-calculations)).
|
If an additional order fills, set stoploss to -10% below the new `open_rate` ([Averaged across all entries](#position-adjust-calculations)).
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
from datetime import datetime, timedelta
|
# Default imports
|
||||||
from freqtrade.persistence import Trade
|
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
@@ -272,9 +275,9 @@ class AwesomeStrategy(IStrategy):
|
|||||||
|
|
||||||
use_custom_stoploss = True
|
use_custom_stoploss = True
|
||||||
|
|
||||||
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
|
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
|
||||||
current_rate: float, current_profit: float, after_fill: bool,
|
current_rate: float, current_profit: float, after_fill: bool,
|
||||||
**kwargs) -> Optional[float]:
|
**kwargs) -> float | None:
|
||||||
|
|
||||||
if after_fill:
|
if after_fill:
|
||||||
# After an additional order, start with a stoploss of 10% below the new open rate
|
# After an additional order, start with a stoploss of 10% below the new open rate
|
||||||
@@ -293,8 +296,7 @@ Use a different stoploss depending on the pair.
|
|||||||
In this example, we'll trail the highest price with 10% trailing stoploss for `ETH/BTC` and `XRP/BTC`, with 5% trailing stoploss for `LTC/BTC` and with 15% for all other pairs.
|
In this example, we'll trail the highest price with 10% trailing stoploss for `ETH/BTC` and `XRP/BTC`, with 5% trailing stoploss for `LTC/BTC` and with 15% for all other pairs.
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
from datetime import datetime
|
# Default imports
|
||||||
from freqtrade.persistence import Trade
|
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
@@ -302,13 +304,13 @@ class AwesomeStrategy(IStrategy):
|
|||||||
|
|
||||||
use_custom_stoploss = True
|
use_custom_stoploss = True
|
||||||
|
|
||||||
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
|
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
|
||||||
current_rate: float, current_profit: float, after_fill: bool,
|
current_rate: float, current_profit: float, after_fill: bool,
|
||||||
**kwargs) -> Optional[float]:
|
**kwargs) -> float | None:
|
||||||
|
|
||||||
if pair in ('ETH/BTC', 'XRP/BTC'):
|
if pair in ("ETH/BTC", "XRP/BTC"):
|
||||||
return -0.10
|
return -0.10
|
||||||
elif pair in ('LTC/BTC'):
|
elif pair in ("LTC/BTC"):
|
||||||
return -0.05
|
return -0.05
|
||||||
return -0.15
|
return -0.15
|
||||||
```
|
```
|
||||||
@@ -320,8 +322,7 @@ Use the initial stoploss until the profit is above 4%, then use a trailing stopl
|
|||||||
Please note that the stoploss can only increase, values lower than the current stoploss are ignored.
|
Please note that the stoploss can only increase, values lower than the current stoploss are ignored.
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
from datetime import datetime, timedelta
|
# Default imports
|
||||||
from freqtrade.persistence import Trade
|
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
@@ -329,9 +330,9 @@ class AwesomeStrategy(IStrategy):
|
|||||||
|
|
||||||
use_custom_stoploss = True
|
use_custom_stoploss = True
|
||||||
|
|
||||||
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
|
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
|
||||||
current_rate: float, current_profit: float, after_fill: bool,
|
current_rate: float, current_profit: float, after_fill: bool,
|
||||||
**kwargs) -> Optional[float]:
|
**kwargs) -> float | None:
|
||||||
|
|
||||||
if current_profit < 0.04:
|
if current_profit < 0.04:
|
||||||
return None # return None to keep using the initial stoploss
|
return None # return None to keep using the initial stoploss
|
||||||
@@ -353,9 +354,7 @@ Instead of continuously trailing behind the current price, this example sets fix
|
|||||||
* Once profit is > 40% - set stoploss to 25% above open price.
|
* Once profit is > 40% - set stoploss to 25% above open price.
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
from datetime import datetime
|
# Default imports
|
||||||
from freqtrade.persistence import Trade
|
|
||||||
from freqtrade.strategy import stoploss_from_open
|
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
@@ -363,9 +362,9 @@ class AwesomeStrategy(IStrategy):
|
|||||||
|
|
||||||
use_custom_stoploss = True
|
use_custom_stoploss = True
|
||||||
|
|
||||||
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
|
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
|
||||||
current_rate: float, current_profit: float, after_fill: bool,
|
current_rate: float, current_profit: float, after_fill: bool,
|
||||||
**kwargs) -> Optional[float]:
|
**kwargs) -> float | None:
|
||||||
|
|
||||||
# evaluate highest to lowest, so that highest possible stop is used
|
# evaluate highest to lowest, so that highest possible stop is used
|
||||||
if current_profit > 0.40:
|
if current_profit > 0.40:
|
||||||
@@ -384,23 +383,25 @@ class AwesomeStrategy(IStrategy):
|
|||||||
Absolute stoploss value may be derived from indicators stored in dataframe. Example uses parabolic SAR below the price as stoploss.
|
Absolute stoploss value may be derived from indicators stored in dataframe. Example uses parabolic SAR below the price as stoploss.
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
|
# Default imports
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
# <...>
|
# <...>
|
||||||
dataframe['sar'] = ta.SAR(dataframe)
|
dataframe["sar"] = ta.SAR(dataframe)
|
||||||
|
|
||||||
use_custom_stoploss = True
|
use_custom_stoploss = True
|
||||||
|
|
||||||
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
|
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
|
||||||
current_rate: float, current_profit: float, after_fill: bool,
|
current_rate: float, current_profit: float, after_fill: bool,
|
||||||
**kwargs) -> Optional[float]:
|
**kwargs) -> float | None:
|
||||||
|
|
||||||
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
||||||
last_candle = dataframe.iloc[-1].squeeze()
|
last_candle = dataframe.iloc[-1].squeeze()
|
||||||
|
|
||||||
# Use parabolic sar as absolute stoploss price
|
# Use parabolic sar as absolute stoploss price
|
||||||
stoploss_price = last_candle['sar']
|
stoploss_price = last_candle["sar"]
|
||||||
|
|
||||||
# Convert absolute price to percentage relative to current_rate
|
# Convert absolute price to percentage relative to current_rate
|
||||||
if stoploss_price < current_rate:
|
if stoploss_price < current_rate:
|
||||||
@@ -429,10 +430,7 @@ Stoploss values returned from `custom_stoploss()` must specify a percentage rela
|
|||||||
|
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
|
# Default imports
|
||||||
from datetime import datetime
|
|
||||||
from freqtrade.persistence import Trade
|
|
||||||
from freqtrade.strategy import IStrategy, stoploss_from_open
|
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
@@ -440,9 +438,9 @@ Stoploss values returned from `custom_stoploss()` must specify a percentage rela
|
|||||||
|
|
||||||
use_custom_stoploss = True
|
use_custom_stoploss = True
|
||||||
|
|
||||||
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
|
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
|
||||||
current_rate: float, current_profit: float, after_fill: bool,
|
current_rate: float, current_profit: float, after_fill: bool,
|
||||||
**kwargs) -> Optional[float]:
|
**kwargs) -> float | None:
|
||||||
|
|
||||||
# once the profit has risen above 10%, keep the stoploss at 7% above the open price
|
# once the profit has risen above 10%, keep the stoploss at 7% above the open price
|
||||||
if current_profit > 0.10:
|
if current_profit > 0.10:
|
||||||
@@ -469,38 +467,34 @@ The helper function `stoploss_from_absolute()` can be used to convert from an ab
|
|||||||
|
|
||||||
??? Example "Returning a stoploss using absolute price from the custom stoploss function"
|
??? Example "Returning a stoploss using absolute price from the custom stoploss function"
|
||||||
|
|
||||||
If we want to trail a stop price at 2xATR below current price we can call `stoploss_from_absolute(current_rate + (side * candle['atr'] * 2), current_rate=current_rate, is_short=trade.is_short, leverage=trade.leverage)`.
|
If we want to trail a stop price at 2xATR below current price we can call `stoploss_from_absolute(current_rate + (side * candle["atr"] * 2), current_rate=current_rate, is_short=trade.is_short, leverage=trade.leverage)`.
|
||||||
For futures, we need to adjust the direction (up or down), as well as adjust for leverage, since the [`custom_stoploss`](strategy-callbacks.md#custom-stoploss) callback returns the ["risk for this trade"](stoploss.md#stoploss-and-leverage) - not the relative price movement.
|
For futures, we need to adjust the direction (up or down), as well as adjust for leverage, since the [`custom_stoploss`](strategy-callbacks.md#custom-stoploss) callback returns the ["risk for this trade"](stoploss.md#stoploss-and-leverage) - not the relative price movement.
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
|
# Default imports
|
||||||
from datetime import datetime
|
|
||||||
from freqtrade.persistence import Trade
|
|
||||||
from freqtrade.strategy import IStrategy, stoploss_from_absolute, timeframe_to_prev_date
|
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
use_custom_stoploss = True
|
use_custom_stoploss = True
|
||||||
|
|
||||||
def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
|
dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
|
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
|
||||||
current_rate: float, current_profit: float, after_fill: bool,
|
current_rate: float, current_profit: float, after_fill: bool,
|
||||||
**kwargs) -> Optional[float]:
|
**kwargs) -> float | None:
|
||||||
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
||||||
trade_date = timeframe_to_prev_date(self.timeframe, trade.open_date_utc)
|
trade_date = timeframe_to_prev_date(self.timeframe, trade.open_date_utc)
|
||||||
candle = dataframe.iloc[-1].squeeze()
|
candle = dataframe.iloc[-1].squeeze()
|
||||||
side = 1 if trade.is_short else -1
|
side = 1 if trade.is_short else -1
|
||||||
return stoploss_from_absolute(current_rate + (side * candle['atr'] * 2),
|
return stoploss_from_absolute(current_rate + (side * candle["atr"] * 2),
|
||||||
current_rate=current_rate,
|
current_rate=current_rate,
|
||||||
is_short=trade.is_short,
|
is_short=trade.is_short,
|
||||||
leverage=trade.leverage)
|
leverage=trade.leverage)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Custom order price rules
|
## Custom order price rules
|
||||||
@@ -520,29 +514,28 @@ Each of these methods are called right before placing an order on the exchange.
|
|||||||
### Custom order entry and exit price example
|
### Custom order entry and exit price example
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
from datetime import datetime, timedelta, timezone
|
# Default imports
|
||||||
from freqtrade.persistence import Trade
|
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
# ... populate_* methods
|
# ... populate_* methods
|
||||||
|
|
||||||
def custom_entry_price(self, pair: str, trade: Optional['Trade'], current_time: datetime, proposed_rate: float,
|
def custom_entry_price(self, pair: str, trade: Trade | None, current_time: datetime, proposed_rate: float,
|
||||||
entry_tag: Optional[str], side: str, **kwargs) -> float:
|
entry_tag: str | None, side: str, **kwargs) -> float:
|
||||||
|
|
||||||
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
|
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
|
||||||
timeframe=self.timeframe)
|
timeframe=self.timeframe)
|
||||||
new_entryprice = dataframe['bollinger_10_lowerband'].iat[-1]
|
new_entryprice = dataframe["bollinger_10_lowerband"].iat[-1]
|
||||||
|
|
||||||
return new_entryprice
|
return new_entryprice
|
||||||
|
|
||||||
def custom_exit_price(self, pair: str, trade: Trade,
|
def custom_exit_price(self, pair: str, trade: Trade,
|
||||||
current_time: datetime, proposed_rate: float,
|
current_time: datetime, proposed_rate: float,
|
||||||
current_profit: float, exit_tag: Optional[str], **kwargs) -> float:
|
current_profit: float, exit_tag: str | None, **kwargs) -> float:
|
||||||
|
|
||||||
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
|
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
|
||||||
timeframe=self.timeframe)
|
timeframe=self.timeframe)
|
||||||
new_exitprice = dataframe['bollinger_10_upperband'].iat[-1]
|
new_exitprice = dataframe["bollinger_10_upperband"].iat[-1]
|
||||||
|
|
||||||
return new_exitprice
|
return new_exitprice
|
||||||
|
|
||||||
@@ -579,8 +572,7 @@ It applies a tight timeout for higher priced assets, while allowing more time to
|
|||||||
The function must return either `True` (cancel order) or `False` (keep order alive).
|
The function must return either `True` (cancel order) or `False` (keep order alive).
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
from datetime import datetime, timedelta
|
# Default imports
|
||||||
from freqtrade.persistence import Trade, Order
|
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
@@ -588,11 +580,11 @@ class AwesomeStrategy(IStrategy):
|
|||||||
|
|
||||||
# Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours.
|
# Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours.
|
||||||
unfilledtimeout = {
|
unfilledtimeout = {
|
||||||
'entry': 60 * 25,
|
"entry": 60 * 25,
|
||||||
'exit': 60 * 25
|
"exit": 60 * 25
|
||||||
}
|
}
|
||||||
|
|
||||||
def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order',
|
def check_entry_timeout(self, pair: str, trade: Trade, order: Order,
|
||||||
current_time: datetime, **kwargs) -> bool:
|
current_time: datetime, **kwargs) -> bool:
|
||||||
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
|
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
|
||||||
return True
|
return True
|
||||||
@@ -603,7 +595,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def check_exit_timeout(self, pair: str, trade: Trade, order: 'Order',
|
def check_exit_timeout(self, pair: str, trade: Trade, order: Order,
|
||||||
current_time: datetime, **kwargs) -> bool:
|
current_time: datetime, **kwargs) -> bool:
|
||||||
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
|
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
|
||||||
return True
|
return True
|
||||||
@@ -620,8 +612,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
### Custom order timeout example (using additional data)
|
### Custom order timeout example (using additional data)
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
from datetime import datetime
|
# Default imports
|
||||||
from freqtrade.persistence import Trade, Order
|
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
@@ -629,24 +620,24 @@ class AwesomeStrategy(IStrategy):
|
|||||||
|
|
||||||
# Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours.
|
# Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours.
|
||||||
unfilledtimeout = {
|
unfilledtimeout = {
|
||||||
'entry': 60 * 25,
|
"entry": 60 * 25,
|
||||||
'exit': 60 * 25
|
"exit": 60 * 25
|
||||||
}
|
}
|
||||||
|
|
||||||
def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order',
|
def check_entry_timeout(self, pair: str, trade: Trade, order: Order,
|
||||||
current_time: datetime, **kwargs) -> bool:
|
current_time: datetime, **kwargs) -> bool:
|
||||||
ob = self.dp.orderbook(pair, 1)
|
ob = self.dp.orderbook(pair, 1)
|
||||||
current_price = ob['bids'][0][0]
|
current_price = ob["bids"][0][0]
|
||||||
# Cancel buy order if price is more than 2% above the order.
|
# Cancel buy order if price is more than 2% above the order.
|
||||||
if current_price > order.price * 1.02:
|
if current_price > order.price * 1.02:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order',
|
def check_exit_timeout(self, pair: str, trade: Trade, order: Order,
|
||||||
current_time: datetime, **kwargs) -> bool:
|
current_time: datetime, **kwargs) -> bool:
|
||||||
ob = self.dp.orderbook(pair, 1)
|
ob = self.dp.orderbook(pair, 1)
|
||||||
current_price = ob['asks'][0][0]
|
current_price = ob["asks"][0][0]
|
||||||
# Cancel sell order if price is more than 2% below the order.
|
# Cancel sell order if price is more than 2% below the order.
|
||||||
if current_price < order.price * 0.98:
|
if current_price < order.price * 0.98:
|
||||||
return True
|
return True
|
||||||
@@ -665,12 +656,14 @@ This are the last methods that will be called before an order is placed.
|
|||||||
`confirm_trade_entry()` can be used to abort a trade entry at the latest second (maybe because the price is not what we expect).
|
`confirm_trade_entry()` can be used to abort a trade entry at the latest second (maybe because the price is not what we expect).
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
|
# Default imports
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
# ... populate_* methods
|
# ... populate_* methods
|
||||||
|
|
||||||
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
|
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
|
||||||
time_in_force: str, current_time: datetime, entry_tag: Optional[str],
|
time_in_force: str, current_time: datetime, entry_tag: str | None,
|
||||||
side: str, **kwargs) -> bool:
|
side: str, **kwargs) -> bool:
|
||||||
"""
|
"""
|
||||||
Called right before placing a entry order.
|
Called right before placing a entry order.
|
||||||
@@ -689,7 +682,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
|
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
|
||||||
:param current_time: datetime object, containing the current datetime
|
:param current_time: datetime object, containing the current datetime
|
||||||
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
|
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
|
||||||
:param side: 'long' or 'short' - indicating the direction of the proposed trade
|
:param side: "long" or "short" - indicating the direction of the proposed trade
|
||||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
:return bool: When True is returned, then the buy-order is placed on the exchange.
|
:return bool: When True is returned, then the buy-order is placed on the exchange.
|
||||||
False aborts the process
|
False aborts the process
|
||||||
@@ -711,8 +704,7 @@ The exit-reasons (if applicable) will be in the following sequence:
|
|||||||
* `trailing_stop_loss`
|
* `trailing_stop_loss`
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
from freqtrade.persistence import Trade
|
# Default imports
|
||||||
|
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
@@ -738,14 +730,14 @@ class AwesomeStrategy(IStrategy):
|
|||||||
or current rate for market orders.
|
or current rate for market orders.
|
||||||
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
|
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
|
||||||
:param exit_reason: Exit reason.
|
:param exit_reason: Exit reason.
|
||||||
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
|
Can be any of ["roi", "stop_loss", "stoploss_on_exchange", "trailing_stop_loss",
|
||||||
'exit_signal', 'force_exit', 'emergency_exit']
|
"exit_signal", "force_exit", "emergency_exit"]
|
||||||
:param current_time: datetime object, containing the current datetime
|
: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.
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
:return bool: When True, then the exit-order is placed on the exchange.
|
:return bool: When True, then the exit-order is placed on the exchange.
|
||||||
False aborts the process
|
False aborts the process
|
||||||
"""
|
"""
|
||||||
if exit_reason == 'force_exit' and trade.calc_profit_ratio(rate) < 0:
|
if exit_reason == "force_exit" and trade.calc_profit_ratio(rate) < 0:
|
||||||
# Reject force-sells with negative profit
|
# Reject force-sells with negative profit
|
||||||
# This is just a sample, please adjust to your needs
|
# This is just a sample, please adjust to your needs
|
||||||
# (this does not necessarily make sense, assuming you know when you're force-selling)
|
# (this does not necessarily make sense, assuming you know when you're force-selling)
|
||||||
@@ -771,10 +763,19 @@ This callback is **not** called when there is an open order (either buy or sell)
|
|||||||
`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.
|
||||||
|
|
||||||
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'`).
|
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.
|
||||||
|
|
||||||
|
!!! Danger "Loose Logic"
|
||||||
|
On dry and live run, this function will be called every `throttle_process_secs` (default to 5s). If you have a loose logic, for example your logic for extra entry is only to check RSI of last candle is below 30, then when such condition fulfilled, your bot will do extra re-entry every 5 secs until either it run out of money, it hit the `max_position_adjustment` limit, or a new candle with RSI more than 30 arrived.
|
||||||
|
|
||||||
|
Same thing also can happen with partial exit. So be sure to have a strict logic and/or check for the last filled order.
|
||||||
|
|
||||||
|
!!! Warning "Backtesting"
|
||||||
|
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.
|
||||||
|
|
||||||
### Increase position
|
### 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).
|
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).
|
||||||
@@ -784,16 +785,22 @@ If there are not enough funds in the wallet (the return value is above `max_stak
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
!!! Note "About stake size"
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
|
||||||
### Decrease position
|
### Decrease position
|
||||||
|
|
||||||
The strategy is expected to return a negative stake_amount (in stake currency) for a partial exit.
|
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 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.
|
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"
|
For a partial exit, it's important to know that the formula used to calculate the amount of the coin for the partial exit order is `amount to be exited partially = negative_stake_amount * trade.amount / trade.stake_amount`, where `negative_stake_amount` is the value returned from the `adjust_trade_position` function. As seen in the formula, the formula doesn't care about current profit/loss of the position. It only cares about `trade.amount` and `trade.stake_amount` which aren't affected by the price movement at all.
|
||||||
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.
|
For example, let's say you buy 2 SHITCOIN/USDT at open rate of 50, which means the trade's stake amount is 100 USDT. Now the price raises to 200 and you want to sell half of it. In that case, you have to return -50% of `trade.stake_amount` (0.5 * 100 USDT) which equals to -50. The bot will calculate the amount it needed to sell, which is `50 * 2 / 100` which equals 1 SHITCOIN/USDT. If you return -200 (50% of 2 * 200), the bot will ignore it since `trade.stake_amount` is only 100 USDT but you asked to sell 200 USDT which means you are asking to sell 4 SHITCOIN/USDT.
|
||||||
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.
|
|
||||||
|
Back to the example above, since current rate is 200, the current USDT value of your trade is now 400 USDT. Let's say you want to partially sell 100 USDT to take out the initial investment and leave the profit in the trade hoping that the price keeps rising. In that case, you have to do a different approach. First, you need to calculate the exact amount you needed to sell. In this case, since you want to sell 100 USDT worth based of current rate, the exact amount you need to partially sell is `100 * 2 / 400` which equals 0.5 SHITCOIN/USDT. Since we know now the exact amount we want to sell (0.5), the value you need to return in the `adjust_trade_position` function is `-amount to be exited partially * trade.stake_amount / trade.amount`, which equals -25. The bot will sell 0.5 SHITCOIN/USDT, keeping 1.5 in trade. You will receive 100 USDT from the partial exit.
|
||||||
|
|
||||||
!!! Warning "Stoploss calculation"
|
!!! 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.
|
||||||
@@ -801,19 +808,13 @@ Returning a value more than the above (so remaining stake_amount would become ne
|
|||||||
|
|
||||||
While `/stopentry` command stops the bot from entering new trades, the position adjustment feature will continue buying new orders on existing trades.
|
While `/stopentry` command stops the bot from entering new trades, the position adjustment feature will continue buying new orders on existing trades.
|
||||||
|
|
||||||
!!! Warning "Backtesting"
|
|
||||||
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.
|
|
||||||
|
|
||||||
!!! Warning "Performance with many position adjustments"
|
!!! 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.
|
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.
|
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.
|
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
|
# Default imports
|
||||||
from typing import Optional, Tuple, Union
|
|
||||||
|
|
||||||
|
|
||||||
class DigDeeperStrategy(IStrategy):
|
class DigDeeperStrategy(IStrategy):
|
||||||
|
|
||||||
@@ -831,8 +832,8 @@ class DigDeeperStrategy(IStrategy):
|
|||||||
|
|
||||||
# This is called when placing the initial order (opening trade)
|
# This is called when placing the initial order (opening trade)
|
||||||
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
|
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
|
||||||
proposed_stake: float, min_stake: Optional[float], max_stake: float,
|
proposed_stake: float, min_stake: float | None, max_stake: float,
|
||||||
leverage: float, entry_tag: Optional[str], side: str,
|
leverage: float, entry_tag: str | None, side: str,
|
||||||
**kwargs) -> float:
|
**kwargs) -> float:
|
||||||
|
|
||||||
# We need to leave most of the funds for possible further DCA orders
|
# We need to leave most of the funds for possible further DCA orders
|
||||||
@@ -841,11 +842,11 @@ class DigDeeperStrategy(IStrategy):
|
|||||||
|
|
||||||
def adjust_trade_position(self, trade: Trade, current_time: datetime,
|
def adjust_trade_position(self, trade: Trade, current_time: datetime,
|
||||||
current_rate: float, current_profit: float,
|
current_rate: float, current_profit: float,
|
||||||
min_stake: Optional[float], max_stake: float,
|
min_stake: float | None, 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
|
**kwargs
|
||||||
) -> Union[Optional[float], Tuple[Optional[float], Optional[str]]]:
|
) -> float | None | tuple[float | None, str | None]:
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
@@ -876,7 +877,7 @@ class DigDeeperStrategy(IStrategy):
|
|||||||
|
|
||||||
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), 'half_profit_5%'
|
return -(trade.stake_amount / 2), "half_profit_5%"
|
||||||
|
|
||||||
if current_profit > -0.05:
|
if current_profit > -0.05:
|
||||||
return None
|
return None
|
||||||
@@ -886,7 +887,7 @@ class DigDeeperStrategy(IStrategy):
|
|||||||
# Only buy when not actively falling price.
|
# Only buy when not actively falling price.
|
||||||
last_candle = dataframe.iloc[-1].squeeze()
|
last_candle = dataframe.iloc[-1].squeeze()
|
||||||
previous_candle = dataframe.iloc[-2].squeeze()
|
previous_candle = dataframe.iloc[-2].squeeze()
|
||||||
if last_candle['close'] < previous_candle['close']:
|
if last_candle["close"] < previous_candle["close"]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
filled_entries = trade.select_filled_orders(trade.entry_side)
|
filled_entries = trade.select_filled_orders(trade.entry_side)
|
||||||
@@ -901,10 +902,10 @@ class DigDeeperStrategy(IStrategy):
|
|||||||
# Hope you have a deep wallet!
|
# Hope you have a deep wallet!
|
||||||
try:
|
try:
|
||||||
# This returns first order stake size
|
# This returns first order stake size
|
||||||
stake_amount = filled_entries[0].stake_amount
|
stake_amount = filled_entries[0].stake_amount_filled
|
||||||
# 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, '1/3rd_increase'
|
return stake_amount, "1/3rd_increase"
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -951,16 +952,15 @@ If the cancellation of the original order fails, then the order will not be repl
|
|||||||
Entry Orders that are cancelled via the above methods will not have this callback called. Be sure to update timeout values to match your expectations.
|
Entry Orders that are cancelled via the above methods will not have this callback called. Be sure to update timeout values to match your expectations.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from freqtrade.persistence import Trade
|
# Default imports
|
||||||
from datetime import timedelta, datetime
|
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
# ... populate_* methods
|
# ... populate_* methods
|
||||||
|
|
||||||
def adjust_entry_price(self, trade: Trade, order: Optional[Order], pair: str,
|
def adjust_entry_price(self, trade: Trade, order: Order | None, pair: str,
|
||||||
current_time: datetime, proposed_rate: float, current_order_rate: float,
|
current_time: datetime, proposed_rate: float, current_order_rate: float,
|
||||||
entry_tag: Optional[str], side: str, **kwargs) -> float:
|
entry_tag: str | None, side: str, **kwargs) -> float:
|
||||||
"""
|
"""
|
||||||
Entry price re-adjustment logic, returning the user desired limit price.
|
Entry price re-adjustment logic, returning the user desired limit price.
|
||||||
This only executes when a order was already placed, still open (unfilled fully or partially)
|
This only executes when a order was already placed, still open (unfilled fully or partially)
|
||||||
@@ -977,13 +977,18 @@ class AwesomeStrategy(IStrategy):
|
|||||||
:param proposed_rate: Rate, calculated based on pricing settings in entry_pricing.
|
:param proposed_rate: Rate, calculated based on pricing settings in entry_pricing.
|
||||||
:param current_order_rate: Rate of the existing order in place.
|
:param current_order_rate: Rate of the existing order in place.
|
||||||
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
|
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
|
||||||
:param side: 'long' or 'short' - indicating the direction of the proposed trade
|
:param side: "long" or "short" - indicating the direction of the proposed trade
|
||||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
:return float: New entry price value if provided
|
:return float: New entry price value if provided
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Limit orders to use and follow SMA200 as price target for the first 10 minutes since entry trigger for BTC/USDT pair.
|
# 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:
|
if (
|
||||||
|
pair == "BTC/USDT"
|
||||||
|
and entry_tag == "long_sma200"
|
||||||
|
and side == "long"
|
||||||
|
and (current_time - timedelta(minutes=10)) <= trade.open_date_utc
|
||||||
|
):
|
||||||
# just cancel the order if it has been filled more than half of the amount
|
# just cancel the order if it has been filled more than half of the amount
|
||||||
if order.filled > order.remaining:
|
if order.filled > order.remaining:
|
||||||
return None
|
return None
|
||||||
@@ -991,7 +996,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
|
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
|
||||||
current_candle = dataframe.iloc[-1].squeeze()
|
current_candle = dataframe.iloc[-1].squeeze()
|
||||||
# desired price
|
# desired price
|
||||||
return current_candle['sma_200']
|
return current_candle["sma_200"]
|
||||||
# default: maintain existing order
|
# default: maintain existing order
|
||||||
return current_order_rate
|
return current_order_rate
|
||||||
```
|
```
|
||||||
@@ -1006,9 +1011,11 @@ Values that are above `max_leverage` will be adjusted to `max_leverage`.
|
|||||||
For markets / exchanges that don't support leverage, this method is ignored.
|
For markets / exchanges that don't support leverage, this method is ignored.
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
|
# Default imports
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
def leverage(self, pair: str, current_time: datetime, current_rate: float,
|
def leverage(self, pair: str, current_time: datetime, current_rate: float,
|
||||||
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
|
proposed_leverage: float, max_leverage: float, entry_tag: str | None, side: str,
|
||||||
**kwargs) -> float:
|
**kwargs) -> float:
|
||||||
"""
|
"""
|
||||||
Customize leverage for each new trade. This method is only called in futures mode.
|
Customize leverage for each new trade. This method is only called in futures mode.
|
||||||
@@ -1019,7 +1026,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
:param proposed_leverage: A leverage proposed by the bot.
|
:param proposed_leverage: A leverage proposed by the bot.
|
||||||
:param max_leverage: Max leverage allowed on this pair
|
:param max_leverage: Max leverage allowed on this pair
|
||||||
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
|
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
|
||||||
:param side: 'long' or 'short' - indicating the direction of the proposed trade
|
:param side: "long" or "short" - indicating the direction of the proposed trade
|
||||||
:return: A leverage amount, which is between 1.0 and max_leverage.
|
:return: A leverage amount, which is between 1.0 and max_leverage.
|
||||||
"""
|
"""
|
||||||
return 1.0
|
return 1.0
|
||||||
@@ -1036,6 +1043,8 @@ It will be called independent of the order type (entry, exit, stoploss or positi
|
|||||||
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.
|
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
|
``` python
|
||||||
|
# Default imports
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
def order_filled(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> None:
|
def order_filled(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -1052,7 +1061,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
last_candle = dataframe.iloc[-1].squeeze()
|
last_candle = dataframe.iloc[-1].squeeze()
|
||||||
|
|
||||||
if (trade.nr_of_successful_entries == 1) and (order.ft_order_side == trade.entry_side):
|
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'])
|
trade.set_custom_data(key="entry_candle_high", value=last_candle["high"])
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
+240
-131
@@ -2,52 +2,93 @@
|
|||||||
|
|
||||||
This page explains how to customize your strategies, add new indicators and set up trading rules.
|
This page explains how to customize your strategies, add new indicators and set up trading rules.
|
||||||
|
|
||||||
Please familiarize yourself with [Freqtrade basics](bot-basics.md) first, which provides overall info on how the bot operates.
|
If you haven't already, please familiarize yourself with:
|
||||||
|
|
||||||
|
- the [Freqtrade strategy 101](strategy-101.md), which provides a quick start to strategy development
|
||||||
|
- the [Freqtrade bot basics](bot-basics.md), which provides overall info on how the bot operates
|
||||||
|
|
||||||
## Develop your own strategy
|
## Develop your own strategy
|
||||||
|
|
||||||
The bot includes a default strategy file.
|
The bot includes a default strategy file.
|
||||||
|
|
||||||
Also, several other strategies are available in the [strategy repository](https://github.com/freqtrade/freqtrade-strategies).
|
Also, several other strategies are available in the [strategy repository](https://github.com/freqtrade/freqtrade-strategies).
|
||||||
|
|
||||||
You will however most likely have your own idea for a strategy.
|
You will however most likely have your own idea for a strategy.
|
||||||
This document intends to help you convert your strategy idea into your own strategy.
|
|
||||||
|
|
||||||
To get started, use `freqtrade new-strategy --strategy AwesomeStrategy` (you can obviously use your own naming for your strategy).
|
This document intends to help you convert your ideas into a working strategy.
|
||||||
This will create a new strategy file from a template, which will be located under `user_data/strategies/AwesomeStrategy.py`.
|
|
||||||
|
### Generating a strategy template
|
||||||
|
|
||||||
|
To get started, you can use the command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
freqtrade new-strategy --strategy AwesomeStrategy
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create a new strategy called `AwesomeStrategy` from a template, which will be located using the filename `user_data/strategies/AwesomeStrategy.py`.
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
This is just a template file, which will most likely not be profitable out of the box.
|
There is a difference between the *name* of the strategy and the filename. In most commands, Freqtrade uses the *name* of the strategy, *not the filename*.
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
The `new-strategy` command generates starting examples which will not be profitable out of the box.
|
||||||
|
|
||||||
??? Hint "Different template levels"
|
??? Hint "Different template levels"
|
||||||
`freqtrade new-strategy` has an additional parameter, `--template`, which controls the amount of pre-build information you get in the created strategy. Use `--template minimal` to get an empty strategy without any indicator examples, or `--template advanced` to get a template with most callbacks defined.
|
`freqtrade new-strategy` has an additional parameter, `--template`, which controls the amount of pre-build information you get in the created strategy. Use `--template minimal` to get an empty strategy without any indicator examples, or `--template advanced` to get a template with more complicated features defined.
|
||||||
|
|
||||||
### Anatomy of a strategy
|
### Anatomy of a strategy
|
||||||
|
|
||||||
A strategy file contains all the information needed to build a good strategy:
|
A strategy file contains all the information needed to build the strategy logic:
|
||||||
|
|
||||||
|
- Candle data in OHLCV format
|
||||||
- Indicators
|
- Indicators
|
||||||
- Entry strategy rules
|
- Entry logic
|
||||||
- Exit strategy rules
|
- Signals
|
||||||
- Minimal ROI recommended
|
- Exit logic
|
||||||
- Stoploss strongly recommended
|
- Signals
|
||||||
|
- Minimal ROI
|
||||||
|
- Callbacks ("custom functions")
|
||||||
|
- Stoploss
|
||||||
|
- Fixed/absolute
|
||||||
|
- Trailing
|
||||||
|
- Callbacks ("custom functions")
|
||||||
|
- Pricing [optional]
|
||||||
|
- Position adjustment [optional]
|
||||||
|
|
||||||
The bot also include a sample strategy called `SampleStrategy` you can update: `user_data/strategies/sample_strategy.py`.
|
The bot includes a sample strategy called `SampleStrategy` that you can use as a basis: `user_data/strategies/sample_strategy.py`.
|
||||||
You can test it with the parameter: `--strategy SampleStrategy`
|
You can test it with the parameter: `--strategy SampleStrategy`. Remember that you use the strategy class name, not the filename.
|
||||||
|
|
||||||
Additionally, there is an attribute called `INTERFACE_VERSION`, which defines the version of the strategy interface the bot should use.
|
Additionally, there is an attribute called `INTERFACE_VERSION`, which defines the version of the strategy interface the bot should use.
|
||||||
The current version is 3 - which is also the default when it's not set explicitly in the strategy.
|
The current version is 3 - which is also the default when it's not set explicitly in the strategy.
|
||||||
|
|
||||||
Future versions will require this to be set.
|
You may see older strategies set to interface version 2, and these will need to be updated to v3 terminology as future versions will require this to be set.
|
||||||
|
|
||||||
|
Starting the bot in dry or live mode is accomplished using the `trade` command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
freqtrade trade --strategy AwesomeStrategy
|
freqtrade trade --strategy AwesomeStrategy
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Bot modes
|
||||||
|
|
||||||
|
Freqtrade strategies can be processed by the Freqtrade bot in 5 main modes:
|
||||||
|
|
||||||
|
- backtesting
|
||||||
|
- hyperopting
|
||||||
|
- dry ("forward testing")
|
||||||
|
- live
|
||||||
|
- FreqAI (not covered here)
|
||||||
|
|
||||||
|
Check the [configuration documentation](configuration.md) about how to set the bot to dry or live mode.
|
||||||
|
|
||||||
|
**Always use dry mode when testing as this gives you an idea of how your strategy will work in reality without risking capital.**
|
||||||
|
|
||||||
|
## Diving in deeper
|
||||||
**For the following section we will use the [user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_strategy.py)
|
**For the following section we will use the [user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_strategy.py)
|
||||||
file as reference.**
|
file as reference.**
|
||||||
|
|
||||||
!!! Note "Strategies and Backtesting"
|
!!! Note "Strategies and Backtesting"
|
||||||
To avoid problems and unexpected differences between Backtesting and dry/live modes, please be aware
|
To avoid problems and unexpected differences between backtesting and dry/live modes, please be aware
|
||||||
that during backtesting the full time range is passed to the `populate_*()` methods at once.
|
that during backtesting the full time range is passed to the `populate_*()` methods at once.
|
||||||
It is therefore best to use vectorized operations (across the whole dataframe, not loops) and
|
It is therefore best to use vectorized operations (across the whole dataframe, not loops) and
|
||||||
avoid index referencing (`df.iloc[-1]`), but instead use `df.shift()` to get to the previous candle.
|
avoid index referencing (`df.iloc[-1]`), but instead use `df.shift()` to get to the previous candle.
|
||||||
@@ -57,14 +98,22 @@ file as reference.**
|
|||||||
needs to take care to avoid having the strategy utilize data from the future.
|
needs to take care to avoid having the strategy utilize data from the future.
|
||||||
Some common patterns for this are listed in the [Common Mistakes](#common-mistakes-when-developing-strategies) section of this document.
|
Some common patterns for this are listed in the [Common Mistakes](#common-mistakes-when-developing-strategies) section of this document.
|
||||||
|
|
||||||
|
??? Hint "Lookahead and recursive analysis"
|
||||||
|
Freqtrade includes two helpful commands to help assess common lookahead (using future data) and
|
||||||
|
recursive bias (variance in indicator values) issues. Before running a strategy in dry or live more,
|
||||||
|
you should always use these commands first. Please check the relevant documentation for
|
||||||
|
[lookahead](lookahead-analysis.md) and [recursive](recursive-analysis.md) analysis.
|
||||||
|
|
||||||
### Dataframe
|
### Dataframe
|
||||||
|
|
||||||
Freqtrade uses [pandas](https://pandas.pydata.org/) to store/provide the candlestick (OHLCV) data.
|
Freqtrade uses [pandas](https://pandas.pydata.org/) to store/provide the candlestick (OHLCV) data.
|
||||||
Pandas is a great library developed for processing large amounts of data.
|
Pandas is a great library developed for processing large amounts of data in tabular format.
|
||||||
|
|
||||||
Each row in a dataframe corresponds to one candle on a chart, with the latest candle always being the last in the dataframe (sorted by date).
|
Each row in a dataframe corresponds to one candle on a chart, with the latest complete candle always being the last in the dataframe (sorted by date).
|
||||||
|
|
||||||
``` output
|
If we were to look at the first few rows of the main dataframe using the pandas `head()` function, we would see:
|
||||||
|
|
||||||
|
```output
|
||||||
> dataframe.head()
|
> dataframe.head()
|
||||||
date open high low close volume
|
date open high low close volume
|
||||||
0 2021-11-09 23:25:00+00:00 67279.67 67321.84 67255.01 67300.97 44.62253
|
0 2021-11-09 23:25:00+00:00 67279.67 67321.84 67255.01 67300.97 44.62253
|
||||||
@@ -74,20 +123,16 @@ Each row in a dataframe corresponds to one candle on a chart, with the latest ca
|
|||||||
4 2021-11-09 23:45:00+00:00 67160.48 67160.48 66901.26 66943.37 111.39292
|
4 2021-11-09 23:45:00+00:00 67160.48 67160.48 66901.26 66943.37 111.39292
|
||||||
```
|
```
|
||||||
|
|
||||||
Pandas provides fast ways to calculate metrics. To benefit from this speed, it's advised to not use loops, but use vectorized methods instead.
|
A dataframe is a table where columns are not single values, but a series of data values. As such, simple python comparisons like the following will not work:
|
||||||
|
|
||||||
Vectorized operations perform calculations across the whole range of data and are therefore, compared to looping through each row, a lot faster when calculating indicators.
|
|
||||||
|
|
||||||
As a dataframe is a table, simple python comparisons like the following will not work
|
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
if dataframe['rsi'] > 30:
|
if dataframe['rsi'] > 30:
|
||||||
dataframe['enter_long'] = 1
|
dataframe['enter_long'] = 1
|
||||||
```
|
```
|
||||||
|
|
||||||
The above section will fail with `The truth value of a Series is ambiguous. [...]`.
|
The above section will fail with `The truth value of a Series is ambiguous [...]`.
|
||||||
|
|
||||||
This must instead be written in a pandas-compatible way, so the operation is performed across the whole dataframe.
|
This must instead be written in a pandas-compatible way, so the operation is performed across the whole dataframe, i.e. `vectorisation`.
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
@@ -97,13 +142,38 @@ This must instead be written in a pandas-compatible way, so the operation is per
|
|||||||
|
|
||||||
With this section, you have a new column in your dataframe, which has `1` assigned whenever RSI is above 30.
|
With this section, you have a new column in your dataframe, which has `1` assigned whenever RSI is above 30.
|
||||||
|
|
||||||
|
Freqtrade uses this new column as an entry signal, where it is assumed that a trade will subsequently open on the next open candle.
|
||||||
|
|
||||||
|
Pandas provides fast ways to calculate metrics, i.e. "vectorisation". To benefit from this speed, it is advised to not use loops, but use vectorized methods instead.
|
||||||
|
|
||||||
|
Vectorized operations perform calculations across the whole range of data and are therefore, compared to looping through each row, a lot faster when calculating indicators.
|
||||||
|
|
||||||
|
??? Hint "Signals vs Trades"
|
||||||
|
- Signals are generated from indicators at candle close, and are intentions to enter a trade.
|
||||||
|
- Trades are orders that are executed (on the exchange in live mode) where a trade will then open as close to next candle open as possible.
|
||||||
|
|
||||||
|
!!! Warning "Trade order assumptions"
|
||||||
|
In backtesting, signals are generated on candle close. Trades are then initiated immeditely on next candle open.
|
||||||
|
|
||||||
|
In dry and live, this may be delayed due to all pair dataframes needing to be analysed first, then trade processing
|
||||||
|
for each of those pairs happens. This means that in dry/live you need to be mindful of having as low a computation
|
||||||
|
delay as possible, usually by running a low number of pairs and having a CPU with a good clock speed.
|
||||||
|
|
||||||
|
#### Why can't I see "real time" candle data?
|
||||||
|
|
||||||
|
Freqtrade does not store incomplete/unfinished candles in the dataframe.
|
||||||
|
|
||||||
|
The use of incomplete data for making strategy decisions is called "repainting" and you might see other platforms allow this.
|
||||||
|
|
||||||
|
Freqtrade does not. Only complete/finished candle data is available in the dataframe.
|
||||||
|
|
||||||
### Customize Indicators
|
### Customize Indicators
|
||||||
|
|
||||||
Buy and sell signals need indicators. You can add more indicators by extending the list contained in the method `populate_indicators()` from your strategy file.
|
Entry and exit signals need indicators. You can add more indicators by extending the list contained in the method `populate_indicators()` from your strategy file.
|
||||||
|
|
||||||
You should only add the indicators used in either `populate_entry_trend()`, `populate_exit_trend()`, or to populate another indicator, otherwise performance may suffer.
|
You should only add the indicators used in either `populate_entry_trend()`, `populate_exit_trend()`, or to populate another indicator, otherwise performance may suffer.
|
||||||
|
|
||||||
It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected.
|
It's important to always return the dataframe from these three functions without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected.
|
||||||
|
|
||||||
Sample:
|
Sample:
|
||||||
|
|
||||||
@@ -124,7 +194,7 @@ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame
|
|||||||
stoch = ta.STOCHF(dataframe)
|
stoch = ta.STOCHF(dataframe)
|
||||||
dataframe['fastd'] = stoch['fastd']
|
dataframe['fastd'] = stoch['fastd']
|
||||||
dataframe['fastk'] = stoch['fastk']
|
dataframe['fastk'] = stoch['fastk']
|
||||||
dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband']
|
dataframe['bb_lower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband']
|
||||||
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
|
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
|
||||||
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
|
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
|
||||||
dataframe['mfi'] = ta.MFI(dataframe)
|
dataframe['mfi'] = ta.MFI(dataframe)
|
||||||
@@ -145,6 +215,8 @@ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame
|
|||||||
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
|
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
|
||||||
dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
|
dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
|
||||||
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
||||||
|
|
||||||
|
# remember to always return the dataframe
|
||||||
return dataframe
|
return dataframe
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -158,17 +230,19 @@ Out of the box, freqtrade installs the following technical libraries:
|
|||||||
|
|
||||||
- [ta-lib](https://ta-lib.github.io/ta-lib-python/)
|
- [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://technical.freqtrade.io)
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
### Strategy startup period
|
### Strategy startup period
|
||||||
|
|
||||||
Most indicators have an instable startup period, in which they are either not available (NaN), or the calculation is incorrect. This can lead to inconsistencies, since Freqtrade does not know how long this instable period should be.
|
Some indicators have an unstable startup period in which there isn't enough candle data to calculate any values (NaN), or the calculation is incorrect. This can lead to inconsistencies, since Freqtrade does not know how long this unstable period is and uses whatever indicator values are in the dataframe.
|
||||||
|
|
||||||
To account for this, the strategy can be assigned the `startup_candle_count` attribute.
|
To account for this, the strategy can be assigned the `startup_candle_count` attribute.
|
||||||
|
|
||||||
This should be set to the maximum number of candles that the strategy requires to calculate stable indicators. In the case where a user includes higher timeframes with informative pairs, the `startup_candle_count` does not necessarily change. The value is the maximum period (in candles) that any of the informatives timeframes need to compute stable indicators.
|
This should be set to the maximum number of candles that the strategy requires to calculate stable indicators. In the case where a user includes higher timeframes with informative pairs, the `startup_candle_count` does not necessarily change. The value is the maximum period (in candles) that any of the informatives timeframes need to compute stable indicators.
|
||||||
|
|
||||||
You can use [recursive-analysis](recursive-analysis.md) to check and find the correct `startup_candle_count` to be used.
|
You can use [recursive-analysis](recursive-analysis.md) to check and find the correct `startup_candle_count` to be used. When recursive analysis shows a variance of 0%, then you can be sure that you have enough startup candle data.
|
||||||
|
|
||||||
In this example strategy, this should be set to 400 (`startup_candle_count = 400`), since the minimum needed history for ema100 calculation to make sure the value is correct is 400 candles.
|
In this example strategy, this should be set to 400 (`startup_candle_count = 400`), since the minimum needed history for ema100 calculation to make sure the value is correct is 400 candles.
|
||||||
|
|
||||||
@@ -195,19 +269,22 @@ Let's try to backtest 1 month (January 2019) of 5m candles using an example stra
|
|||||||
freqtrade backtesting --timerange 20190101-20190201 --timeframe 5m
|
freqtrade backtesting --timerange 20190101-20190201 --timeframe 5m
|
||||||
```
|
```
|
||||||
|
|
||||||
Assuming `startup_candle_count` is set to 400, backtesting knows it needs 400 candles to generate valid buy signals. It will load data from `20190101 - (400 * 5m)` - which is ~2018-12-30 11:40:00.
|
Assuming `startup_candle_count` is set to 400, backtesting knows it needs 400 candles to generate valid entry signals. It will load data from `20190101 - (400 * 5m)` - which is ~2018-12-30 11:40:00.
|
||||||
If this data is available, indicators will be calculated with this extended timerange. The instable startup period (up to 2019-01-01 00:00:00) will then be removed before starting backtesting.
|
|
||||||
|
|
||||||
!!! Note
|
If this data is available, indicators will be calculated with this extended timerange. The unstable startup period (up to 2019-01-01 00:00:00) will then be removed before backtesting is carried out.
|
||||||
If data for the startup period is not available, then the timerange will be adjusted to account for this startup period - so Backtesting would start at 2019-01-02 09:20:00.
|
|
||||||
|
!!! Note "Unavailable startup candle data"
|
||||||
|
If data for the startup period is not available, then the timerange will be adjusted to account for this startup period. In our example, backtesting would then start from 2019-01-02 09:20:00.
|
||||||
|
|
||||||
### Entry signal rules
|
### Entry signal rules
|
||||||
|
|
||||||
Edit the method `populate_entry_trend()` in your strategy file to update your entry strategy.
|
Edit the method `populate_entry_trend()` in your strategy file to update your entry strategy.
|
||||||
|
|
||||||
It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected.
|
It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected. The strategy may then produce invalid values, or cease to work entirely.
|
||||||
|
|
||||||
This method will also define a new column, `"enter_long"` (`"enter_short"` for shorts), which needs to contain 1 for entries, and 0 for "no action". `enter_long` is a mandatory column that must be set even if the strategy is shorting only.
|
This method will also define a new column, `"enter_long"` (`"enter_short"` for shorts), which needs to contain `1` for entries, and `0` for "no action". `enter_long` is a mandatory column that must be set even if the strategy is shorting only.
|
||||||
|
|
||||||
|
You can name your entry signals by using the `"enter_tag"` column, which can help debug and assess your strategy later.
|
||||||
|
|
||||||
Sample from `user_data/strategies/sample_strategy.py`:
|
Sample from `user_data/strategies/sample_strategy.py`:
|
||||||
|
|
||||||
@@ -232,12 +309,15 @@ def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFram
|
|||||||
```
|
```
|
||||||
|
|
||||||
??? Note "Enter short trades"
|
??? Note "Enter short trades"
|
||||||
Short-entries can be created by setting `enter_short` (corresponds to `enter_long` for long trades).
|
Short entries can be created by setting `enter_short` (corresponds to `enter_long` for long trades).
|
||||||
The `enter_tag` column remains identical.
|
The `enter_tag` column remains identical.
|
||||||
Short-trades need to be supported by your exchange and market configuration!
|
Shorting needs to be supported by your exchange and market configuration!
|
||||||
Please make sure to set [`can_short`]() appropriately on your strategy if you intend to short.
|
Also, make sure you set [`can_short`](#can-short) appropriately on your strategy if you intend to short.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
# allow both long and short trades
|
||||||
|
can_short = True
|
||||||
|
|
||||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
(
|
||||||
@@ -261,17 +341,21 @@ def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFram
|
|||||||
```
|
```
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
Buying requires sellers to buy from - therefore volume needs to be > 0 (`dataframe['volume'] > 0`) to make sure that the bot does not buy/sell in no-activity periods.
|
Buying requires sellers to buy from. Therefore volume needs to be > 0 (`dataframe['volume'] > 0`) to make sure that the bot does not buy/sell in no-activity periods.
|
||||||
|
|
||||||
### Exit signal rules
|
### Exit signal rules
|
||||||
|
|
||||||
Edit the method `populate_exit_trend()` into your strategy file to update your exit strategy.
|
Edit the method `populate_exit_trend()` into your strategy file to update your exit strategy.
|
||||||
|
|
||||||
The exit-signal can be suppressed by setting `use_exit_signal` to false in the configuration or strategy.
|
The exit-signal can be suppressed by setting `use_exit_signal` to false in the configuration or strategy.
|
||||||
|
|
||||||
`use_exit_signal` will not influence [signal collision rules](#colliding-signals) - which will still apply and can prevent entries.
|
`use_exit_signal` will not influence [signal collision rules](#colliding-signals) - which will still apply and can prevent entries.
|
||||||
|
|
||||||
It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected.
|
It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected. The strategy may then produce invalid values, or cease to work entirely.
|
||||||
|
|
||||||
This method will also define a new column, `"exit_long"` (`"exit_short"` for shorts), which needs to contain 1 for exits, and 0 for "no action".
|
This method will also define a new column, `"exit_long"` (`"exit_short"` for shorts), which needs to contain `1` for exits, and `0` for "no action".
|
||||||
|
|
||||||
|
You can name your exit signals by using the `"exit_tag"` column, which can help debug and assess your strategy later.
|
||||||
|
|
||||||
Sample from `user_data/strategies/sample_strategy.py`:
|
Sample from `user_data/strategies/sample_strategy.py`:
|
||||||
|
|
||||||
@@ -295,11 +379,15 @@ def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame
|
|||||||
```
|
```
|
||||||
|
|
||||||
??? Note "Exit short trades"
|
??? Note "Exit short trades"
|
||||||
Short-exits can be created by setting `exit_short` (corresponds to `exit_long`).
|
Short exits can be created by setting `exit_short` (corresponds to `exit_long`).
|
||||||
The `exit_tag` column remains identical.
|
The `exit_tag` column remains identical.
|
||||||
Short-trades need to be supported by your exchange and market configuration!
|
Shorting needs to be supported by your exchange and market configuration!
|
||||||
|
Also, make sure you set [`can_short`](#can-short) appropriately on your strategy if you intend to short.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
# allow both long and short trades
|
||||||
|
can_short = True
|
||||||
|
|
||||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
(
|
||||||
@@ -322,9 +410,9 @@ def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame
|
|||||||
|
|
||||||
### Minimal ROI
|
### Minimal ROI
|
||||||
|
|
||||||
This dict defines the minimal Return On Investment (ROI) a trade should reach before exiting, independent from the exit signal.
|
The `minimal_roi` strategy variable defines the minimal Return On Investment (ROI) a trade should reach before exiting, independent from the exit signal.
|
||||||
|
|
||||||
It is of the following format, with the dict key (left side of the colon) being the minutes passed since the trade opened, and the value (right side of the colon) being the percentage.
|
It is of the following format, i.e. a python `dict`, with the dict key (left side of the colon) being the minutes passed since the trade opened, and the value (right side of the colon) being the percentage.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
minimal_roi = {
|
minimal_roi = {
|
||||||
@@ -344,14 +432,19 @@ The above configuration would therefore mean:
|
|||||||
|
|
||||||
The calculation does include fees.
|
The calculation does include fees.
|
||||||
|
|
||||||
|
#### Disabling minimal ROI
|
||||||
|
|
||||||
To disable ROI completely, set it to an empty dictionary:
|
To disable ROI completely, set it to an empty dictionary:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
minimal_roi = {}
|
minimal_roi = {}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Using calculations in minimal ROI
|
||||||
|
|
||||||
To use times based on candle duration (timeframe), the following snippet can be handy.
|
To use times based on candle duration (timeframe), the following snippet can be handy.
|
||||||
This will allow you to change the timeframe for the strategy, and ROI times will still be set as candles (e.g. after 3 candles ...)
|
|
||||||
|
This will allow you to change the timeframe for the strategy, but the minimal ROI times will still be set as candles, e.g. after 3 candles.
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
from freqtrade.exchange import timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
@@ -368,9 +461,9 @@ class AwesomeStrategy(IStrategy):
|
|||||||
```
|
```
|
||||||
|
|
||||||
??? info "Orders that don't fill immediately"
|
??? 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.
|
`minimal_roi` will take the `trade.open_date` as reference, which is the time the trade was initialized, i.e. when 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()`.
|
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 price 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.
|
The time used will still be from the initial `trade.open_date` (when the initial order was first placed), not from the newly placed or adjusted order date.
|
||||||
|
|
||||||
### Stoploss
|
### Stoploss
|
||||||
|
|
||||||
@@ -386,33 +479,44 @@ For the full documentation on stoploss features, look at the dedicated [stoploss
|
|||||||
|
|
||||||
### Timeframe
|
### Timeframe
|
||||||
|
|
||||||
This is the set of candles the bot should download and use for the analysis.
|
This is the periodicity of candles the bot should use in the strategy.
|
||||||
|
|
||||||
Common values are `"1m"`, `"5m"`, `"15m"`, `"1h"`, however all values supported by your exchange should work.
|
Common values are `"1m"`, `"5m"`, `"15m"`, `"1h"`, however all values supported by your exchange should work.
|
||||||
|
|
||||||
Please note that the same entry/exit signals may work well with one timeframe, but not with the others.
|
Please note that the same entry/exit signals may work well with one timeframe, but not with others.
|
||||||
|
|
||||||
This setting is accessible within the strategy methods as the `self.timeframe` attribute.
|
This setting is accessible within the strategy methods as the `self.timeframe` attribute.
|
||||||
|
|
||||||
### Can short
|
### Can short
|
||||||
|
|
||||||
To use short signals in futures markets, you will have to let us know to do so by setting `can_short=True`.
|
To use short signals in futures markets, you will have to set `can_short = True`.
|
||||||
|
|
||||||
Strategies which enable this will fail to load on spot markets.
|
Strategies which enable this will fail to load on spot markets.
|
||||||
Disabling of this will have short signals ignored (also in futures markets).
|
|
||||||
|
If you have `1` values in the `enter_short` column to raise short signals, setting `can_short = False` (which is the default) will mean that these short signals are ignored, even if you have specified futures markets in your configuration.
|
||||||
|
|
||||||
### Metadata dict
|
### Metadata dict
|
||||||
|
|
||||||
The metadata-dict (available for `populate_entry_trend`, `populate_exit_trend`, `populate_indicators`) contains additional information.
|
The `metadata` dict (available for `populate_entry_trend`, `populate_exit_trend`, `populate_indicators`) contains additional information.
|
||||||
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` (or `XRP/BTC:BTC` for futures markets).
|
||||||
|
|
||||||
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 functions in your strategy.
|
||||||
Instead, have a look at the [Storing information](strategy-advanced.md#storing-information-persistent) section.
|
|
||||||
|
Instead, please check the [Storing information](strategy-advanced.md#storing-information-persistent) section.
|
||||||
|
|
||||||
|
--8<-- "includes/strategy-imports.md"
|
||||||
|
|
||||||
## Strategy file loading
|
## Strategy file loading
|
||||||
|
|
||||||
By default, freqtrade will attempt to load strategies from all `.py` files within `user_data/strategies`.
|
By default, freqtrade will attempt to load strategies from all `.py` files within the `userdir` (default `user_data/strategies`).
|
||||||
|
|
||||||
Assuming your strategy is called `AwesomeStrategy`, stored in the file `user_data/strategies/AwesomeStrategy.py`, then you can start freqtrade with `freqtrade trade --strategy AwesomeStrategy`.
|
Assuming your strategy is called `AwesomeStrategy`, stored in the file `user_data/strategies/AwesomeStrategy.py`, then you can start freqtrade in dry (or live, depending on your configuration) mode with:
|
||||||
Note that we're using the class-name, not the file name.
|
|
||||||
|
```bash
|
||||||
|
freqtrade trade --strategy AwesomeStrategy`
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that we're using the class name, not the file name.
|
||||||
|
|
||||||
You can use `freqtrade list-strategies` to see a list of all strategies Freqtrade is able to load (all strategies in the correct folder).
|
You can use `freqtrade list-strategies` to see a list of all strategies Freqtrade is able to load (all strategies in the correct folder).
|
||||||
It will also include a "status" field, highlighting potential problems.
|
It will also include a "status" field, highlighting potential problems.
|
||||||
@@ -424,9 +528,11 @@ It will also include a "status" field, highlighting potential problems.
|
|||||||
|
|
||||||
### Get data for non-tradeable pairs
|
### Get data for non-tradeable pairs
|
||||||
|
|
||||||
Data for additional, informative pairs (reference pairs) can be beneficial for some strategies.
|
Data for additional, informative pairs (reference pairs) can be beneficial for some strategies to see data on a wider timeframe.
|
||||||
|
|
||||||
OHLCV data for these pairs will be downloaded as part of the regular whitelist refresh process and is available via `DataProvider` just as other pairs (see below).
|
OHLCV data for these pairs will be downloaded as part of the regular whitelist refresh process and is available via `DataProvider` just as other pairs (see below).
|
||||||
These parts will **not** be traded unless they are also specified in the pair whitelist, or have been selected by Dynamic Whitelisting.
|
|
||||||
|
These pairs will **not** be traded unless they are also specified in the pair whitelist, or have been selected by Dynamic Whitelisting, e.g. `VolumePairlist`.
|
||||||
|
|
||||||
The pairs need to be specified as tuples in the format `("pair", "timeframe")`, with pair as the first and timeframe as the second argument.
|
The pairs need to be specified as tuples in the format `("pair", "timeframe")`, with pair as the first and timeframe as the second argument.
|
||||||
|
|
||||||
@@ -466,18 +572,24 @@ A full sample can be found [in the DataProvider section](#complete-data-provider
|
|||||||
|
|
||||||
### Informative pairs decorator (`@informative()`)
|
### Informative pairs decorator (`@informative()`)
|
||||||
|
|
||||||
In most common case it is possible to easily define informative pairs by using a decorator. All decorated `populate_indicators_*` methods run in isolation,
|
To easily define informative pairs, use the `@informative` decorator. All decorated `populate_indicators_*` methods run in isolation,
|
||||||
not having access to data from other informative pairs, in the end all informative dataframes are merged and passed to main `populate_indicators()` method.
|
and do not have access to data from other informative pairs. However, all informative dataframes for each pair are merged and passed to main `populate_indicators()` method.
|
||||||
When hyperopting, use of hyperoptable parameter `.value` attribute is not supported. Please use `.range` attribute. See [optimizing an indicator parameter](hyperopt.md#optimizing-an-indicator-parameter)
|
|
||||||
for more information.
|
!!! Note
|
||||||
|
Do not use the `@informative` decorator if you need to use data from one informative pair when generating another informative pair. Instead, define informative pairs manually as described [in the DataProvider section](#complete-data-provider-sample).
|
||||||
|
|
||||||
|
When hyperopting, use of the hyperoptable parameter `.value` attribute is not supported. Please use the `.range` attribute. See [optimizing an indicator parameter](hyperopt.md#optimizing-an-indicator-parameter) for more information.
|
||||||
|
|
||||||
??? info "Full documentation"
|
??? info "Full documentation"
|
||||||
``` python
|
``` python
|
||||||
def informative(timeframe: str, asset: str = '',
|
def informative(
|
||||||
fmt: Optional[Union[str, Callable[[KwArg(str)], str]]] = None,
|
timeframe: str,
|
||||||
*,
|
asset: str = "",
|
||||||
candle_type: Optional[CandleType] = None,
|
fmt: str | Callable[[Any], str] | None = None,
|
||||||
ffill: bool = True) -> Callable[[PopulateIndicators], PopulateIndicators]:
|
*,
|
||||||
|
candle_type: CandleType | str | None = None,
|
||||||
|
ffill: bool = True,
|
||||||
|
) -> Callable[[PopulateIndicators], PopulateIndicators]:
|
||||||
"""
|
"""
|
||||||
A decorator for populate_indicators_Nn(self, dataframe, metadata), allowing these functions to
|
A decorator for populate_indicators_Nn(self, dataframe, metadata), allowing these functions to
|
||||||
define informative indicators.
|
define informative indicators.
|
||||||
@@ -566,10 +678,6 @@ for more information.
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! Note
|
|
||||||
Do not use `@informative` decorator if you need to use data of one informative pair when generating another informative pair. Instead, define informative pairs
|
|
||||||
manually as described [in the DataProvider section](#complete-data-provider-sample).
|
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
Use string formatting when accessing informative dataframes of other pairs. This will allow easily changing stake currency in config without having to adjust strategy code.
|
Use string formatting when accessing informative dataframes of other pairs. This will allow easily changing stake currency in config without having to adjust strategy code.
|
||||||
|
|
||||||
@@ -590,18 +698,15 @@ for more information.
|
|||||||
Alternatively column renaming may be used to remove stake currency from column names: `@informative('1h', 'BTC/{stake}', fmt='{base}_{column}_{timeframe}')`.
|
Alternatively column renaming may be used to remove stake currency from column names: `@informative('1h', 'BTC/{stake}', fmt='{base}_{column}_{timeframe}')`.
|
||||||
|
|
||||||
!!! Warning "Duplicate method names"
|
!!! Warning "Duplicate method names"
|
||||||
Methods tagged with `@informative()` decorator must always have unique names! Re-using same name (for example when copy-pasting already defined informative method)
|
Methods tagged with the `@informative()` decorator must always have unique names! Reusing the same name (for example when copy-pasting already defined informative methods) will overwrite previously defined methods and not produce any errors due to limitations of Python programming language. In such cases you will find that indicators created in methods higher up in the strategy file are not available in the dataframe. Carefully review method names and make sure they are unique!
|
||||||
will overwrite previously defined method and not produce any errors due to limitations of Python programming language. In such cases you will find that indicators
|
|
||||||
created in earlier-defined methods are not available in the dataframe. Carefully review method names and make sure they are unique!
|
|
||||||
|
|
||||||
### *merge_informative_pair()*
|
### *merge_informative_pair()*
|
||||||
|
|
||||||
This method helps you merge an informative pair to a regular dataframe without lookahead bias.
|
This method helps you merge an informative pair to the regular main dataframe safely and consistently, without lookahead bias.
|
||||||
It's there to help you merge the dataframe in a safe and consistent way.
|
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- Rename the columns for you to create unique columns
|
- Rename the columns to create unique columns
|
||||||
- Merge the dataframe without lookahead bias
|
- Merge the dataframe without lookahead bias
|
||||||
- Forward-fill (optional)
|
- Forward-fill (optional)
|
||||||
|
|
||||||
@@ -652,20 +757,20 @@ All columns of the informative dataframe will be available on the returning data
|
|||||||
```
|
```
|
||||||
|
|
||||||
!!! Warning "Informative timeframe < timeframe"
|
!!! Warning "Informative timeframe < timeframe"
|
||||||
Using informative timeframes smaller than the dataframe timeframe is not recommended with this method, as it will not use any of the additional information this would provide.
|
Using informative timeframes smaller than the main dataframe timeframe is not recommended with this method, as it will not use any of the additional information this would provide.
|
||||||
To use the more detailed information properly, more advanced methods should be applied (which are out of scope for freqtrade documentation, as it'll depend on the respective need).
|
To use the more detailed information properly, more advanced methods should be applied (which are out of scope for this documentation).
|
||||||
|
|
||||||
## Additional data (DataProvider)
|
## Additional data (DataProvider)
|
||||||
|
|
||||||
The strategy provides access to the `DataProvider`. This allows you to get additional data to use in your strategy.
|
The strategy provides access to the `DataProvider`. This allows you to get additional data to use in your strategy.
|
||||||
|
|
||||||
All methods return `None` in case of failure (do not raise an exception).
|
All methods return `None` in case of failure, i.e. failures do not raise an exception.
|
||||||
|
|
||||||
Please always check the mode of operation to select the correct method to get data (samples see below).
|
Please always check the mode of operation to select the correct method to get data (see below for examples).
|
||||||
|
|
||||||
!!! Warning "Hyperopt"
|
!!! Warning "Hyperopt Limitations"
|
||||||
Dataprovider is available during hyperopt, however it can only be used in `populate_indicators()` within a strategy.
|
The DataProvider is available during hyperopt, however it can only be used in `populate_indicators()` **within a strategy**, not within a hyperopt class file.
|
||||||
It is not available in `populate_buy()` and `populate_sell()` methods, nor in `populate_indicators()`, if this method located in the hyperopt file.
|
It is also not available in `populate_entry_trend()` and `populate_exit_trend()` methods.
|
||||||
|
|
||||||
### Possible options for DataProvider
|
### Possible options for DataProvider
|
||||||
|
|
||||||
@@ -691,30 +796,31 @@ for pair, timeframe in self.dp.available_pairs:
|
|||||||
|
|
||||||
### *current_whitelist()*
|
### *current_whitelist()*
|
||||||
|
|
||||||
Imagine you've developed a strategy that trades the `5m` timeframe using signals generated from a `1d` timeframe on the top 10 volume pairs by volume.
|
Imagine you've developed a strategy that trades the `5m` timeframe using signals generated from a `1d` timeframe on the top 10 exchange pairs by volume.
|
||||||
|
|
||||||
The strategy might look something like this:
|
The strategy logic might look something like this:
|
||||||
|
|
||||||
*Scan through the top 10 pairs by volume using the `VolumePairList` every 5 minutes and use a 14 day RSI to buy and sell.*
|
*Scan through the top 10 pairs by volume using the `VolumePairList` every 5 minutes and use a 14 day RSI to enter and exit.*
|
||||||
|
|
||||||
Due to the limited available data, it's very difficult to resample `5m` candles into daily candles for use in a 14 day RSI. Most exchanges limit us to just 500-1000 candles which effectively gives us around 1.74 daily candles. We need 14 days at least!
|
Due to the limited available data, it's very difficult to resample `5m` candles into daily candles for use in a 14 day RSI. Most exchanges limit users to just 500-1000 candles which effectively gives us around 1.74 daily candles. We need 14 days at least!
|
||||||
|
|
||||||
Since we can't resample the data we will have to use an informative pair; and since the whitelist will be dynamic we don't know which pair(s) to use.
|
Since we can't resample the data we will have to use an informative pair, and since the whitelist will be dynamic we don't know which pair(s) to use! We have a problem!
|
||||||
|
|
||||||
This is where calling `self.dp.current_whitelist()` comes in handy.
|
This is where calling `self.dp.current_whitelist()` comes in handy to retrieve only those pairs in the whitelist.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def informative_pairs(self):
|
def informative_pairs(self):
|
||||||
|
|
||||||
# get access to all pairs available in whitelist.
|
# get access to all pairs available in whitelist.
|
||||||
pairs = self.dp.current_whitelist()
|
pairs = self.dp.current_whitelist()
|
||||||
# Assign tf to each pair so they can be downloaded and cached for strategy.
|
# Assign timeframe to each pair so they can be downloaded and cached for strategy.
|
||||||
informative_pairs = [(pair, '1d') for pair in pairs]
|
informative_pairs = [(pair, '1d') for pair in pairs]
|
||||||
return informative_pairs
|
return informative_pairs
|
||||||
```
|
```
|
||||||
|
|
||||||
??? Note "Plotting with current_whitelist"
|
??? Note "Plotting with current_whitelist"
|
||||||
Current whitelist is not supported for `plot-dataframe`, as this command is usually used by providing an explicit pairlist - and would therefore make the return values of this method misleading.
|
Current whitelist is not supported for `plot-dataframe`, as this command is usually used by providing an explicit pairlist and would therefore make the return values of this method misleading.
|
||||||
|
It's also not supported for FreqUI visualization in [webserver mode](utils.md#webserver-mode), as the configuration for webserver mode doesn't require a pairlist to be set.
|
||||||
|
|
||||||
### *get_pair_dataframe(pair, timeframe)*
|
### *get_pair_dataframe(pair, timeframe)*
|
||||||
|
|
||||||
@@ -755,7 +861,7 @@ if self.dp.runmode.value in ('live', 'dry_run'):
|
|||||||
dataframe['best_ask'] = ob['asks'][0][0]
|
dataframe['best_ask'] = ob['asks'][0][0]
|
||||||
```
|
```
|
||||||
|
|
||||||
The orderbook structure is aligned with the order structure from [ccxt](https://github.com/ccxt/ccxt/wiki/Manual#order-book-structure), so the result will look as follows:
|
The orderbook structure is aligned with the order structure from [ccxt](https://github.com/ccxt/ccxt/wiki/Manual#order-book-structure), so the result will be formatted as follows:
|
||||||
|
|
||||||
``` js
|
``` js
|
||||||
{
|
{
|
||||||
@@ -773,7 +879,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 use 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 up-to-date 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.
|
||||||
@@ -790,12 +896,12 @@ if self.dp.runmode.value in ('live', 'dry_run'):
|
|||||||
|
|
||||||
!!! Warning
|
!!! Warning
|
||||||
Although the ticker data structure is a part of the ccxt Unified Interface, the values returned by this method can
|
Although the ticker data structure is a part of the ccxt Unified Interface, the values returned by this method can
|
||||||
vary for different exchanges. For instance, many exchanges do not return `vwap` values, some exchanges
|
vary for different exchanges. For instance, many exchanges do not return `vwap` values, and some exchanges
|
||||||
does not always fills in the `last` field (so it can be None), etc. So you need to carefully verify the ticker
|
do not always fill in the `last` field (so it can be None), etc. So you need to carefully verify the ticker
|
||||||
data returned from the exchange and add appropriate error handling / defaults.
|
data returned from the exchange and add appropriate error handling / defaults.
|
||||||
|
|
||||||
!!! Warning "Warning about backtesting"
|
!!! Warning "Warning about backtesting"
|
||||||
This method will always return up-to-date values - so usage during backtesting / hyperopt without runmode checks will lead to wrong results.
|
This method will always return up-to-date / real-time values. As such, usage during backtesting / hyperopt without runmode checks will lead to wrong results, e.g. your whole dataframe will contain the same single value in all rows.
|
||||||
|
|
||||||
### Send Notification
|
### Send Notification
|
||||||
|
|
||||||
@@ -814,7 +920,7 @@ Notifications will only be sent in trading modes (Live/Dry-run) - so this method
|
|||||||
!!! Warning "Spamming"
|
!!! Warning "Spamming"
|
||||||
You can spam yourself pretty good by setting `always_send=True` in this method. Use this with great care and only in conditions you know will not happen throughout a candle to avoid a message every 5 seconds.
|
You can spam yourself pretty good by setting `always_send=True` in this method. Use this with great care and only in conditions you know will not happen throughout a candle to avoid a message every 5 seconds.
|
||||||
|
|
||||||
### Complete Data-provider sample
|
### Complete DataProvider sample
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from freqtrade.strategy import IStrategy, merge_informative_pair
|
from freqtrade.strategy import IStrategy, merge_informative_pair
|
||||||
@@ -881,14 +987,14 @@ class SampleStrategy(IStrategy):
|
|||||||
|
|
||||||
## Additional data (Wallets)
|
## Additional data (Wallets)
|
||||||
|
|
||||||
The strategy provides access to the `wallets` object. This contains the current balances on the exchange.
|
The strategy provides access to the `wallets` object. This contains the current balances of your wallets/accounts on the exchange.
|
||||||
|
|
||||||
!!! Note "Backtesting / Hyperopt"
|
!!! Note "Backtesting / Hyperopt"
|
||||||
Wallets behaves differently depending on the function it's called.
|
Wallets behaves differently depending on the function from which it is called.
|
||||||
Within `populate_*()` methods, it'll return the full wallet as configured.
|
Within `populate_*()` methods, it'll return the full wallet as configured.
|
||||||
Within [callbacks](strategy-callbacks.md), you'll get the wallet state corresponding to the actual simulated wallet at that point in the simulation process.
|
Within [callbacks](strategy-callbacks.md), you'll get the wallet state corresponding to the actual simulated wallet at that point in the simulation process.
|
||||||
|
|
||||||
Please always check if `wallets` is available to avoid failures during backtesting.
|
Always check if `wallets` is available to avoid failures during backtesting.
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
if self.wallets:
|
if self.wallets:
|
||||||
@@ -907,15 +1013,15 @@ if self.wallets:
|
|||||||
|
|
||||||
## Additional data (Trades)
|
## Additional data (Trades)
|
||||||
|
|
||||||
A history of Trades can be retrieved in the strategy by querying the database.
|
A history of trades can be retrieved in the strategy by querying the database.
|
||||||
|
|
||||||
At the top of the file, import Trade.
|
At the top of the file, import the required object:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
```
|
```
|
||||||
|
|
||||||
The following example queries for the current pair and trades from today, however other filters can easily be added.
|
The following example queries trades from today for the current pair (`metadata['pair']`). Other filters can easily be added.
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
trades = Trade.get_trades_proxy(pair=metadata['pair'],
|
trades = Trade.get_trades_proxy(pair=metadata['pair'],
|
||||||
@@ -933,7 +1039,9 @@ For a full list of available methods, please consult the [Trade object](trade-ob
|
|||||||
|
|
||||||
## Prevent trades from happening for a specific pair
|
## Prevent trades from happening for a specific pair
|
||||||
|
|
||||||
Freqtrade locks pairs automatically for the current candle (until that candle is over) when a pair is sold, preventing an immediate re-buy of that pair.
|
Freqtrade locks pairs automatically for the current candle (until that candle is over) when a pair exits, preventing an immediate re-entry of that pair.
|
||||||
|
|
||||||
|
This is to prevent "waterfalls" of many and frequent trades within a single candle.
|
||||||
|
|
||||||
Locked pairs will show the message `Pair <pair> is currently locked.`.
|
Locked pairs will show the message `Pair <pair> is currently locked.`.
|
||||||
|
|
||||||
@@ -944,7 +1052,7 @@ Sometimes it may be desired to lock a pair after certain events happen (e.g. mul
|
|||||||
Freqtrade has an easy method to do this from within the strategy, by calling `self.lock_pair(pair, until, [reason])`.
|
Freqtrade has an easy method to do this from within the strategy, by calling `self.lock_pair(pair, until, [reason])`.
|
||||||
`until` must be a datetime object in the future, after which trading will be re-enabled for that pair, while `reason` is an optional string detailing why the pair was locked.
|
`until` must be a datetime object in the future, after which trading will be re-enabled for that pair, while `reason` is an optional string detailing why the pair was locked.
|
||||||
|
|
||||||
Locks can also be lifted manually, by calling `self.unlock_pair(pair)` or `self.unlock_reason(<reason>)` - providing reason the pair was locked with.
|
Locks can also be lifted manually, by calling `self.unlock_pair(pair)` or `self.unlock_reason(<reason>)`, providing the reason the pair was unlocked.
|
||||||
`self.unlock_reason(<reason>)` will unlock all pairs currently locked with the provided reason.
|
`self.unlock_reason(<reason>)` will unlock all pairs currently locked with the provided reason.
|
||||||
|
|
||||||
To verify if a pair is currently locked, use `self.is_pair_locked(pair)`.
|
To verify if a pair is currently locked, use `self.is_pair_locked(pair)`.
|
||||||
@@ -953,7 +1061,7 @@ To verify if a pair is currently locked, use `self.is_pair_locked(pair)`.
|
|||||||
Locked pairs will always be rounded up to the next candle. So assuming a `5m` timeframe, a lock with `until` set to 10:18 will lock the pair until the candle from 10:15-10:20 will be finished.
|
Locked pairs will always be rounded up to the next candle. So assuming a `5m` timeframe, a lock with `until` set to 10:18 will lock the pair until the candle from 10:15-10:20 will be finished.
|
||||||
|
|
||||||
!!! Warning
|
!!! Warning
|
||||||
Manually locking pairs is not available during backtesting, only locks via Protections are allowed.
|
Manually locking pairs is not available during backtesting. Only locks via Protections are allowed.
|
||||||
|
|
||||||
#### Pair locking example
|
#### Pair locking example
|
||||||
|
|
||||||
@@ -963,7 +1071,7 @@ from datetime import timedelta, datetime, timezone
|
|||||||
# Put the above lines a the top of the strategy file, next to all the other imports
|
# Put the above lines a the top of the strategy file, next to all the other imports
|
||||||
# --------
|
# --------
|
||||||
|
|
||||||
# Within populate indicators (or populate_buy):
|
# Within populate indicators (or populate_entry_trend):
|
||||||
if self.config['runmode'].value in ('live', 'dry_run'):
|
if self.config['runmode'].value in ('live', 'dry_run'):
|
||||||
# fetch closed trades for the last 2 days
|
# fetch closed trades for the last 2 days
|
||||||
trades = Trade.get_trades_proxy(
|
trades = Trade.get_trades_proxy(
|
||||||
@@ -976,9 +1084,9 @@ if self.config['runmode'].value in ('live', 'dry_run'):
|
|||||||
self.lock_pair(metadata['pair'], until=datetime.now(timezone.utc) + timedelta(hours=12))
|
self.lock_pair(metadata['pair'], until=datetime.now(timezone.utc) + timedelta(hours=12))
|
||||||
```
|
```
|
||||||
|
|
||||||
## Print created dataframe
|
## Print the main dataframe
|
||||||
|
|
||||||
To inspect the created dataframe, you can issue a print-statement in either `populate_entry_trend()` or `populate_exit_trend()`.
|
To inspect the current main dataframe, you can issue a print-statement in either `populate_entry_trend()` or `populate_exit_trend()`.
|
||||||
You may also want to print the pair so it's clear what data is currently shown.
|
You may also want to print the pair so it's clear what data is currently shown.
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
@@ -998,29 +1106,30 @@ def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFram
|
|||||||
return dataframe
|
return dataframe
|
||||||
```
|
```
|
||||||
|
|
||||||
Printing more than a few rows is also possible (simply use `print(dataframe)` instead of `print(dataframe.tail())`), however not recommended, as that will be very verbose (~500 lines per pair every 5 seconds).
|
Printing more than a few rows is also possible by using `print(dataframe)` instead of `print(dataframe.tail())`. However this is not recommended, as can results in a lot of output (~500 lines per pair every 5 seconds).
|
||||||
|
|
||||||
## Common mistakes when developing strategies
|
## Common mistakes when developing strategies
|
||||||
|
|
||||||
### Peeking into the future while backtesting
|
### Looking into the future while backtesting
|
||||||
|
|
||||||
Backtesting analyzes the whole time-range at once for performance reasons. Because of this, strategy authors need to make sure that strategies do not look-ahead into the future.
|
Backtesting analyzes the whole dataframe timerange at once for performance reasons. Because of this, strategy authors need to make sure that strategies do not lookahead into the future, i.e. using data that would not be available in dry or live mode.
|
||||||
This is a common pain-point, which can cause huge differences between backtesting and dry/live run methods, since they all use data which is not available during dry/live runs, so these strategies will perform well during backtesting, but will fail / perform badly in real conditions.
|
|
||||||
|
|
||||||
The following lists some common patterns which should be avoided to prevent frustration:
|
This is a common pain-point, which can cause huge differences between backtesting and dry/live run methods. Strategies that look into the future will perform well during backtesting, often with incredible profits or winrates, but will fail or perform badly in real conditions.
|
||||||
|
|
||||||
|
The following list contains some common patterns which should be avoided to prevent frustration:
|
||||||
|
|
||||||
- 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 `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 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 `.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 functions that use all dataframe or column values, e.g. `dataframe['mean_volume'] = dataframe['volume'].mean()`. As backtesting uses the full dataframe, at any point in the dataframe, the `'mean_volume'` series would include data from the future. Use rolling() calculations instead, e.g. `dataframe['volume'].rolling(<window>).mean()`.
|
||||||
- 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 period interval, so moves data from an hour boundary to the start of the hour. Use `.resample('1h', label='right')` instead.
|
||||||
|
|
||||||
!!! Tip "Identifying problems"
|
!!! Tip "Identifying problems"
|
||||||
You may also want to check the 2 helper commands [lookahead-analysis](lookahead-analysis.md) and [recursive-analysis](recursive-analysis.md), which can each help you figure out problems with your strategy in different ways.
|
You should always use the two helper commands [lookahead-analysis](lookahead-analysis.md) and [recursive-analysis](recursive-analysis.md), which can each help you figure out problems with your strategy in different ways.
|
||||||
Please treat them as what they are - helpers to identify most common problems. A negative result of each does not guarantee that there's none of the above errors included.
|
Please treat them as what they are - helpers to identify most common problems. A negative result of each does not guarantee that there are none of the above errors included.
|
||||||
|
|
||||||
### Colliding signals
|
### Colliding signals
|
||||||
|
|
||||||
When conflicting signals collide (e.g. both `'enter_long'` and `'exit_long'` are 1), freqtrade will do nothing and ignore the entry signal. This will avoid trades that enter, and exit immediately. Obviously, this can potentially lead to missed entries.
|
When conflicting signals collide (e.g. both `'enter_long'` and `'exit_long'` are set to `1`), freqtrade will do nothing and ignore the entry signal. This will avoid trades that enter, and exit immediately. Obviously, this can potentially lead to missed entries.
|
||||||
|
|
||||||
The following rules apply, and entry signals will be ignored if more than one of the 3 signals is set:
|
The following rules apply, and entry signals will be ignored if more than one of the 3 signals is set:
|
||||||
|
|
||||||
@@ -1029,11 +1138,11 @@ The following rules apply, and entry signals will be ignored if more than one of
|
|||||||
|
|
||||||
## Further strategy ideas
|
## Further strategy ideas
|
||||||
|
|
||||||
To get additional Ideas for strategies, head over to the [strategy repository](https://github.com/freqtrade/freqtrade-strategies). Feel free to use them as they are - but results will depend on the current market situation, pairs used etc. - therefore please backtest the strategy for your exchange/desired pairs first, evaluate carefully, use at your own risk.
|
To get additional ideas for strategies, head over to the [strategy repository](https://github.com/freqtrade/freqtrade-strategies). Feel free to use them as examples, but results will depend on the current market situation, pairs used, etc. Therefore, these strategies should be considered only for learning purposes, not real world trading. Please backtest the strategy for your exchange/desired pairs first, then dry run to evaluate carefully, and use at your own risk.
|
||||||
Feel free to use any of them as inspiration for your own strategies.
|
|
||||||
We're happy to accept Pull Requests containing new Strategies to that repo.
|
|
||||||
|
|
||||||
## Next step
|
Feel free to use any of them as inspiration for your own strategies. We're happy to accept Pull Requests containing new strategies to the repository.
|
||||||
|
|
||||||
|
## Next steps
|
||||||
|
|
||||||
Now you have a perfect strategy you probably want to backtest it.
|
Now you have a perfect strategy you probably want to backtest it.
|
||||||
Your next step is to learn [How to use the Backtesting](backtesting.md).
|
Your next step is to learn [how to use backtesting](backtesting.md).
|
||||||
|
|||||||
@@ -13,19 +13,22 @@ Please follow the [documentation](https://www.freqtrade.io/en/stable/data-downlo
|
|||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
# Change directory
|
# Change directory
|
||||||
# Modify this cell to insure that the output shows the correct path.
|
# Modify this cell to insure that the output shows the correct path.
|
||||||
# Define all paths relative to the project root shown in the cell output
|
# Define all paths relative to the project root shown in the cell output
|
||||||
project_root = "somedir/freqtrade"
|
project_root = "somedir/freqtrade"
|
||||||
i=0
|
i = 0
|
||||||
try:
|
try:
|
||||||
os.chdir(project_root)
|
os.chdir(project_root)
|
||||||
assert Path('LICENSE').is_file()
|
if not Path("LICENSE").is_file():
|
||||||
except:
|
i = 0
|
||||||
while i<4 and (not Path('LICENSE').is_file()):
|
while i < 4 and (not Path("LICENSE").is_file()):
|
||||||
os.chdir(Path(Path.cwd(), '../'))
|
os.chdir(Path(Path.cwd(), "../"))
|
||||||
i+=1
|
i += 1
|
||||||
project_root = Path.cwd()
|
project_root = Path.cwd()
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("Please define the project root relative to the current directory")
|
||||||
print(Path.cwd())
|
print(Path.cwd())
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -35,6 +38,7 @@ print(Path.cwd())
|
|||||||
```python
|
```python
|
||||||
from freqtrade.configuration import Configuration
|
from freqtrade.configuration import Configuration
|
||||||
|
|
||||||
|
|
||||||
# Customize these according to your needs.
|
# Customize these according to your needs.
|
||||||
|
|
||||||
# Initialize empty configuration object
|
# Initialize empty configuration object
|
||||||
@@ -58,12 +62,14 @@ pair = "BTC/USDT"
|
|||||||
from freqtrade.data.history import load_pair_history
|
from freqtrade.data.history import load_pair_history
|
||||||
from freqtrade.enums import CandleType
|
from freqtrade.enums import CandleType
|
||||||
|
|
||||||
candles = load_pair_history(datadir=data_location,
|
|
||||||
timeframe=config["timeframe"],
|
candles = load_pair_history(
|
||||||
pair=pair,
|
datadir=data_location,
|
||||||
data_format = "json", # Make sure to update this to your data
|
timeframe=config["timeframe"],
|
||||||
candle_type=CandleType.SPOT,
|
pair=pair,
|
||||||
)
|
data_format="json", # Make sure to update this to your data
|
||||||
|
candle_type=CandleType.SPOT,
|
||||||
|
)
|
||||||
|
|
||||||
# Confirm success
|
# Confirm success
|
||||||
print(f"Loaded {len(candles)} rows of data for {pair} from {data_location}")
|
print(f"Loaded {len(candles)} rows of data for {pair} from {data_location}")
|
||||||
@@ -76,14 +82,16 @@ candles.head()
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
# Load strategy using values set above
|
# Load strategy using values set above
|
||||||
from freqtrade.resolvers import StrategyResolver
|
|
||||||
from freqtrade.data.dataprovider import DataProvider
|
from freqtrade.data.dataprovider import DataProvider
|
||||||
|
from freqtrade.resolvers import StrategyResolver
|
||||||
|
|
||||||
|
|
||||||
strategy = StrategyResolver.load_strategy(config)
|
strategy = StrategyResolver.load_strategy(config)
|
||||||
strategy.dp = DataProvider(config, None, None)
|
strategy.dp = DataProvider(config, None, None)
|
||||||
strategy.ft_bot_start()
|
strategy.ft_bot_start()
|
||||||
|
|
||||||
# Generate buy/sell signals using strategy
|
# Generate buy/sell signals using strategy
|
||||||
df = strategy.analyze_ticker(candles, {'pair': pair})
|
df = strategy.analyze_ticker(candles, {"pair": pair})
|
||||||
df.tail()
|
df.tail()
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -102,7 +110,7 @@ df.tail()
|
|||||||
```python
|
```python
|
||||||
# Report results
|
# Report results
|
||||||
print(f"Generated {df['enter_long'].sum()} entry signals")
|
print(f"Generated {df['enter_long'].sum()} entry signals")
|
||||||
data = df.set_index('date', drop=False)
|
data = df.set_index("date", drop=False)
|
||||||
data.tail()
|
data.tail()
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -119,10 +127,13 @@ Analyze a trades dataframe (also used below for plotting)
|
|||||||
```python
|
```python
|
||||||
from freqtrade.data.btanalysis import load_backtest_data, load_backtest_stats
|
from freqtrade.data.btanalysis import load_backtest_data, load_backtest_stats
|
||||||
|
|
||||||
|
|
||||||
# if backtest_dir points to a directory, it'll automatically load the last backtest file.
|
# if backtest_dir points to a directory, it'll automatically load the last backtest file.
|
||||||
backtest_dir = config["user_data_dir"] / "backtest_results"
|
backtest_dir = config["user_data_dir"] / "backtest_results"
|
||||||
# backtest_dir can also point to a specific file
|
# backtest_dir can also point to a specific file
|
||||||
# backtest_dir = config["user_data_dir"] / "backtest_results/backtest-result-2020-07-01_20-04-22.json"
|
# backtest_dir = (
|
||||||
|
# config["user_data_dir"] / "backtest_results/backtest-result-2020-07-01_20-04-22.json"
|
||||||
|
# )
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -131,24 +142,24 @@ backtest_dir = config["user_data_dir"] / "backtest_results"
|
|||||||
# This contains all information used to generate the backtest result.
|
# This contains all information used to generate the backtest result.
|
||||||
stats = load_backtest_stats(backtest_dir)
|
stats = load_backtest_stats(backtest_dir)
|
||||||
|
|
||||||
strategy = 'SampleStrategy'
|
strategy = "SampleStrategy"
|
||||||
# All statistics are available per strategy, so if `--strategy-list` was used during backtest, this will be reflected here as well.
|
# All statistics are available per strategy, so if `--strategy-list` was used during backtest,
|
||||||
|
# this will be reflected here as well.
|
||||||
# Example usages:
|
# Example usages:
|
||||||
print(stats['strategy'][strategy]['results_per_pair'])
|
print(stats["strategy"][strategy]["results_per_pair"])
|
||||||
# Get pairlist used for this backtest
|
# Get pairlist used for this backtest
|
||||||
print(stats['strategy'][strategy]['pairlist'])
|
print(stats["strategy"][strategy]["pairlist"])
|
||||||
# Get market change (average change of all pairs from start to end of the backtest period)
|
# Get market change (average change of all pairs from start to end of the backtest period)
|
||||||
print(stats['strategy'][strategy]['market_change'])
|
print(stats["strategy"][strategy]["market_change"])
|
||||||
# Maximum drawdown ()
|
# Maximum drawdown ()
|
||||||
print(stats['strategy'][strategy]['max_drawdown'])
|
print(stats["strategy"][strategy]["max_drawdown_abs"])
|
||||||
# Maximum drawdown start and end
|
# Maximum drawdown start and end
|
||||||
print(stats['strategy'][strategy]['drawdown_start'])
|
print(stats["strategy"][strategy]["drawdown_start"])
|
||||||
print(stats['strategy'][strategy]['drawdown_end'])
|
print(stats["strategy"][strategy]["drawdown_end"])
|
||||||
|
|
||||||
|
|
||||||
# Get strategy comparison (only relevant if multiple strategies were compared)
|
# Get strategy comparison (only relevant if multiple strategies were compared)
|
||||||
print(stats['strategy_comparison'])
|
print(stats["strategy_comparison"])
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -166,24 +177,25 @@ trades.groupby("pair")["exit_reason"].value_counts()
|
|||||||
```python
|
```python
|
||||||
# Plotting equity line (starting with 0 on day 1 and adding daily profit for each backtested day)
|
# Plotting equity line (starting with 0 on day 1 and adding daily profit for each backtested day)
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
import plotly.express as px
|
||||||
|
|
||||||
from freqtrade.configuration import Configuration
|
from freqtrade.configuration import Configuration
|
||||||
from freqtrade.data.btanalysis import load_backtest_stats
|
from freqtrade.data.btanalysis import load_backtest_stats
|
||||||
import plotly.express as px
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
# strategy = 'SampleStrategy'
|
# strategy = 'SampleStrategy'
|
||||||
# config = Configuration.from_files(["user_data/config.json"])
|
# config = Configuration.from_files(["user_data/config.json"])
|
||||||
# backtest_dir = config["user_data_dir"] / "backtest_results"
|
# backtest_dir = config["user_data_dir"] / "backtest_results"
|
||||||
|
|
||||||
stats = load_backtest_stats(backtest_dir)
|
stats = load_backtest_stats(backtest_dir)
|
||||||
strategy_stats = stats['strategy'][strategy]
|
strategy_stats = stats["strategy"][strategy]
|
||||||
|
|
||||||
df = pd.DataFrame(columns=['dates','equity'], data=strategy_stats['daily_profit'])
|
df = pd.DataFrame(columns=["dates", "equity"], data=strategy_stats["daily_profit"])
|
||||||
df['equity_daily'] = df['equity'].cumsum()
|
df["equity_daily"] = df["equity"].cumsum()
|
||||||
|
|
||||||
fig = px.line(df, x="dates", y="equity_daily")
|
fig = px.line(df, x="dates", y="equity_daily")
|
||||||
fig.show()
|
fig.show()
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Load live trading results into a pandas dataframe
|
### Load live trading results into a pandas dataframe
|
||||||
@@ -194,6 +206,7 @@ In case you did already some trading and want to analyze your performance
|
|||||||
```python
|
```python
|
||||||
from freqtrade.data.btanalysis import load_trades_from_db
|
from freqtrade.data.btanalysis import load_trades_from_db
|
||||||
|
|
||||||
|
|
||||||
# Fetch trades from database
|
# Fetch trades from database
|
||||||
trades = load_trades_from_db("sqlite:///tradesv3.sqlite")
|
trades = load_trades_from_db("sqlite:///tradesv3.sqlite")
|
||||||
|
|
||||||
@@ -202,7 +215,7 @@ trades.groupby("pair")["exit_reason"].value_counts()
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Analyze the loaded trades for trade parallelism
|
## Analyze the loaded trades for trade parallelism
|
||||||
This can be useful to find the best `max_open_trades` parameter, when used with backtesting in conjunction with `--disable-max-market-positions`.
|
This can be useful to find the best `max_open_trades` parameter, when used with backtesting in conjunction with a very high `max_open_trades` setting.
|
||||||
|
|
||||||
`analyze_trade_parallelism()` returns a timeseries dataframe with an "open_trades" column, specifying the number of open trades for each candle.
|
`analyze_trade_parallelism()` returns a timeseries dataframe with an "open_trades" column, specifying the number of open trades for each candle.
|
||||||
|
|
||||||
@@ -210,8 +223,9 @@ This can be useful to find the best `max_open_trades` parameter, when used with
|
|||||||
```python
|
```python
|
||||||
from freqtrade.data.btanalysis import analyze_trade_parallelism
|
from freqtrade.data.btanalysis import analyze_trade_parallelism
|
||||||
|
|
||||||
|
|
||||||
# Analyze the above
|
# Analyze the above
|
||||||
parallel_trades = analyze_trade_parallelism(trades, '5m')
|
parallel_trades = analyze_trade_parallelism(trades, "5m")
|
||||||
|
|
||||||
parallel_trades.plot()
|
parallel_trades.plot()
|
||||||
```
|
```
|
||||||
@@ -222,23 +236,23 @@ Freqtrade offers interactive plotting capabilities based on plotly.
|
|||||||
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from freqtrade.plot.plotting import generate_candlestick_graph
|
from freqtrade.plot.plotting import generate_candlestick_graph
|
||||||
|
|
||||||
|
|
||||||
# Limit graph period to keep plotly quick and reactive
|
# Limit graph period to keep plotly quick and reactive
|
||||||
|
|
||||||
# Filter trades to one pair
|
# Filter trades to one pair
|
||||||
trades_red = trades.loc[trades['pair'] == pair]
|
trades_red = trades.loc[trades["pair"] == pair]
|
||||||
|
|
||||||
data_red = data['2019-06-01':'2019-06-10']
|
data_red = data["2019-06-01":"2019-06-10"]
|
||||||
# Generate candlestick graph
|
# Generate candlestick graph
|
||||||
graph = generate_candlestick_graph(pair=pair,
|
graph = generate_candlestick_graph(
|
||||||
data=data_red,
|
pair=pair,
|
||||||
trades=trades_red,
|
data=data_red,
|
||||||
indicators1=['sma20', 'ema50', 'ema55'],
|
trades=trades_red,
|
||||||
indicators2=['rsi', 'macd', 'macdsignal', 'macdhist']
|
indicators1=["sma20", "ema50", "ema55"],
|
||||||
)
|
indicators2=["rsi", "macd", "macdsignal", "macdhist"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -248,7 +262,6 @@ graph = generate_candlestick_graph(pair=pair,
|
|||||||
|
|
||||||
# Render graph in a separate window
|
# Render graph in a separate window
|
||||||
graph.show(renderer="browser")
|
graph.show(renderer="browser")
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Plot average profit per trade as distribution graph
|
## Plot average profit per trade as distribution graph
|
||||||
@@ -257,12 +270,12 @@ graph.show(renderer="browser")
|
|||||||
```python
|
```python
|
||||||
import plotly.figure_factory as ff
|
import plotly.figure_factory as ff
|
||||||
|
|
||||||
|
|
||||||
hist_data = [trades.profit_ratio]
|
hist_data = [trades.profit_ratio]
|
||||||
group_labels = ['profit_ratio'] # name of the dataset
|
group_labels = ["profit_ratio"] # name of the dataset
|
||||||
|
|
||||||
fig = ff.create_distplot(hist_data, group_labels, bin_size=0.01)
|
fig = ff.create_distplot(hist_data, group_labels, bin_size=0.01)
|
||||||
fig.show()
|
fig.show()
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Feel free to submit an issue or Pull Request enhancing this document if you would like to share ideas on how to best analyze the data.
|
Feel free to submit an issue or Pull Request enhancing this document if you would like to share ideas on how to best analyze the data.
|
||||||
|
|||||||
@@ -214,8 +214,8 @@ class AwesomeStrategy(IStrategy):
|
|||||||
``` python hl_lines="4"
|
``` python hl_lines="4"
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
|
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
|
||||||
proposed_stake: float, min_stake: Optional[float], max_stake: float,
|
proposed_stake: float, min_stake: float | None, max_stake: float,
|
||||||
entry_tag: Optional[str], side: str, **kwargs) -> float:
|
entry_tag: str | None, side: str, **kwargs) -> float:
|
||||||
# ...
|
# ...
|
||||||
return proposed_stake
|
return proposed_stake
|
||||||
```
|
```
|
||||||
@@ -237,7 +237,7 @@ After:
|
|||||||
``` python hl_lines="4"
|
``` python hl_lines="4"
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
|
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
|
||||||
time_in_force: str, current_time: datetime, entry_tag: Optional[str],
|
time_in_force: str, current_time: datetime, entry_tag: str | None,
|
||||||
side: str, **kwargs) -> bool:
|
side: str, **kwargs) -> bool:
|
||||||
return True
|
return True
|
||||||
```
|
```
|
||||||
@@ -280,8 +280,8 @@ After:
|
|||||||
|
|
||||||
``` python hl_lines="3"
|
``` python hl_lines="3"
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
def custom_entry_price(self, pair: str, trade: Optional[Trade], current_time: datetime, proposed_rate: float,
|
def custom_entry_price(self, pair: str, trade: Trade | None, current_time: datetime, proposed_rate: float,
|
||||||
entry_tag: Optional[str], side: str, **kwargs) -> float:
|
entry_tag: str | None, side: str, **kwargs) -> float:
|
||||||
return proposed_rate
|
return proposed_rate
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -312,7 +312,7 @@ After:
|
|||||||
``` python hl_lines="5 7"
|
``` python hl_lines="5 7"
|
||||||
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
|
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
|
||||||
current_rate: float, current_profit: float, after_fill: bool,
|
current_rate: float, current_profit: float, after_fill: bool,
|
||||||
**kwargs) -> Optional[float]:
|
**kwargs) -> float | None:
|
||||||
# once the profit has risen above 10%, keep the stoploss at 7% above the open price
|
# once the profit has risen above 10%, keep the stoploss at 7% above the open price
|
||||||
if current_profit > 0.10:
|
if current_profit > 0.10:
|
||||||
return stoploss_from_open(0.07, current_profit, is_short=trade.is_short)
|
return stoploss_from_open(0.07, current_profit, is_short=trade.is_short)
|
||||||
@@ -329,7 +329,7 @@ After:
|
|||||||
`order_time_in_force` attributes changed from `"buy"` to `"entry"` and `"sell"` to `"exit"`.
|
`order_time_in_force` attributes changed from `"buy"` to `"entry"` and `"sell"` to `"exit"`.
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
order_time_in_force: Dict = {
|
order_time_in_force: dict = {
|
||||||
"buy": "gtc",
|
"buy": "gtc",
|
||||||
"sell": "gtc",
|
"sell": "gtc",
|
||||||
}
|
}
|
||||||
@@ -338,7 +338,7 @@ After:
|
|||||||
After:
|
After:
|
||||||
|
|
||||||
``` python hl_lines="2 3"
|
``` python hl_lines="2 3"
|
||||||
order_time_in_force: Dict = {
|
order_time_in_force: dict = {
|
||||||
"entry": "GTC",
|
"entry": "GTC",
|
||||||
"exit": "GTC",
|
"exit": "GTC",
|
||||||
}
|
}
|
||||||
@@ -780,7 +780,7 @@ class MyCoolFreqaiModel(BaseRegressionModel):
|
|||||||
|
|
||||||
def predict(
|
def predict(
|
||||||
self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs
|
self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs
|
||||||
) -> Tuple[DataFrame, npt.NDArray[np.int_]]:
|
) -> tuple[DataFrame, npt.NDArray[np.int_]]:
|
||||||
|
|
||||||
# ... your custom stuff
|
# ... your custom stuff
|
||||||
|
|
||||||
|
|||||||
@@ -11,3 +11,11 @@
|
|||||||
.rst-versions .rst-other-versions {
|
.rst-versions .rst-other-versions {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.md-version__list {
|
||||||
|
font-weight: 500 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#available-endpoints ~ .md-typeset__scrollwrap .md-typeset__table th:first-of-type {
|
||||||
|
width: 35% !important;
|
||||||
|
}
|
||||||
|
|||||||
+99
-74
@@ -45,22 +45,42 @@ Get your "Id", you will use it for the config parameter `chat_id`.
|
|||||||
|
|
||||||
#### Use Group id
|
#### Use Group id
|
||||||
|
|
||||||
You can use bots in telegram groups by just adding them to the group. You can find the group id by first adding a [RawDataBot](https://telegram.me/rawdatabot) to your group. The Group id is shown as id in the `"chat"` section, which the RawDataBot will send to you:
|
To get the group ID, you can add the bot to the group, start freqtrade, and issue a `/tg_info` command.
|
||||||
|
This will return the group id to you, without having to use some random bot.
|
||||||
|
While "chat_id" is still required, it doesn't need to be set to this particular group id for this command.
|
||||||
|
|
||||||
|
The response will also contain the "topic_id" if necessary - both in a format ready to copy/paste into your configuration.
|
||||||
|
|
||||||
``` json
|
``` json
|
||||||
"chat":{
|
{
|
||||||
"id":-1001332619709
|
"enabled": true,
|
||||||
|
"token": "********",
|
||||||
|
"chat_id": "-1001332619709",
|
||||||
|
"topic_id": "122"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For the Freqtrade configuration, you can then use the full value (including `-` if it's there) as string:
|
For the Freqtrade configuration, you can then use the full value (including `-` ) 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 unpleasant 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.
|
||||||
|
|
||||||
|
##### Group Topic ID
|
||||||
|
|
||||||
|
To use a specific topic in a group, you can use the `topic_id` parameter in the configuration. This will allow you to use the bot in a specific topic in a group.
|
||||||
|
Without this, the bot will always respond to the general channel in the group if topics are enabled for a group chat.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"chat_id": "-1001332619709",
|
||||||
|
"topic_id": "3"
|
||||||
|
```
|
||||||
|
|
||||||
|
Similar to the group-id - you can use `/tg_info` from the topic/thread to get the correct topic-id.
|
||||||
|
|
||||||
## Control telegram noise
|
## Control telegram noise
|
||||||
|
|
||||||
Freqtrade provides means to control the verbosity of your telegram bot.
|
Freqtrade provides means to control the verbosity of your telegram bot.
|
||||||
@@ -93,9 +113,12 @@ Example configuration showing the different settings:
|
|||||||
"trailing_stop_loss": "on",
|
"trailing_stop_loss": "on",
|
||||||
"stop_loss": "on",
|
"stop_loss": "on",
|
||||||
"stoploss_on_exchange": "on",
|
"stoploss_on_exchange": "on",
|
||||||
"custom_exit": "silent",
|
"custom_exit": "silent", // custom_exit without specifying an exit reason
|
||||||
"partial_exit": "on"
|
"partial_exit": "on",
|
||||||
|
// "custom_exit_message": "silent", // Disable individual custom exit reasons
|
||||||
|
"*": "off" // Disable all other exit reasons
|
||||||
},
|
},
|
||||||
|
// "exit": "off", // Simplistic configuration to disable all exit messages
|
||||||
"exit_cancel": "on",
|
"exit_cancel": "on",
|
||||||
"exit_fill": "off",
|
"exit_fill": "off",
|
||||||
"protection_trigger": "off",
|
"protection_trigger": "off",
|
||||||
@@ -108,16 +131,16 @@ Example configuration showing the different settings:
|
|||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
`entry` notifications are sent when the order is placed, while `entry_fill` notifications are sent when the order is filled on the exchange.
|
* `entry` notifications are sent when the order is placed, while `entry_fill` notifications are sent when the order is filled on the exchange.
|
||||||
`exit` notifications are sent when the order is placed, while `exit_fill` notifications are sent when the order is filled on the exchange.
|
* `exit` notifications are sent when the order is placed, while `exit_fill` notifications are sent when the order is filled on the exchange.
|
||||||
`*_fill` notifications are off by default and must be explicitly enabled.
|
Exit messages (`exit` and `exit_fill`) can be further controlled at individual exit reasons level, with the specific exit reason as the key. the default for all exit reasons is `on` - but can be configured via special `*` key - which will act as a wildcard for all exit reasons that are not explicitly defined.
|
||||||
`protection_trigger` notifications are sent when a protection triggers and `protection_trigger_global` notifications trigger when global protections are triggered.
|
* `*_fill` notifications are off by default and must be explicitly enabled.
|
||||||
`strategy_msg` - Receive notifications from the strategy, sent via `self.dp.send_msg()` from the strategy [more details](strategy-customization.md#send-notification).
|
* `protection_trigger` notifications are sent when a protection triggers and `protection_trigger_global` notifications trigger when global protections are triggered.
|
||||||
`show_candle` - show candle values as part of entry/exit messages. Only possible values are `"ohlc"` or `"off"`.
|
* `strategy_msg` - Receive notifications from the strategy, sent via `self.dp.send_msg()` from the strategy [more details](strategy-customization.md#send-notification).
|
||||||
|
* `show_candle` - show candle values as part of entry/exit messages. Only possible values are `"ohlc"` or `"off"`.
|
||||||
`balance_dust_level` will define what the `/balance` command takes as "dust" - Currencies with a balance below this will be shown.
|
* `balance_dust_level` will define what the `/balance` command takes as "dust" - Currencies with a balance below this will be shown.
|
||||||
`allow_custom_messages` completely disable strategy messages.
|
* `allow_custom_messages` completely disable strategy messages.
|
||||||
`reload` allows you to disable reload-buttons on selected messages.
|
* `reload` allows you to disable reload-buttons on selected messages.
|
||||||
|
|
||||||
## Create a custom keyboard (command shortcut buttons)
|
## Create a custom keyboard (command shortcut buttons)
|
||||||
|
|
||||||
@@ -231,23 +254,23 @@ Once all positions are sold, run `/stop` to completely stop the bot.
|
|||||||
`/reload_config` resets "max_open_trades" to the value set in the configuration and resets this command.
|
`/reload_config` resets "max_open_trades" to the value set in the configuration and resets this command.
|
||||||
|
|
||||||
!!! Warning
|
!!! Warning
|
||||||
The stop-buy signal is ONLY active while the bot is running, and is not persisted anyway, so restarting the bot will cause this to reset.
|
The stop-buy signal is ONLY active while the bot is running, and is not persisted anyway, so restarting the bot will cause this to reset.
|
||||||
|
|
||||||
### /status
|
### /status
|
||||||
|
|
||||||
For each open trade, the bot will send you the following message.
|
For each open trade, the bot will send you the following message.
|
||||||
Enter Tag is configurable via Strategy.
|
Enter Tag is configurable via Strategy.
|
||||||
|
|
||||||
> **Trade ID:** `123` `(since 1 days ago)`
|
> **Trade ID:** `123` `(since 1 days ago)`
|
||||||
> **Current Pair:** CVC/BTC
|
> **Current Pair:** CVC/BTC
|
||||||
> **Direction:** Long
|
> **Direction:** Long
|
||||||
> **Leverage:** 1.0
|
> **Leverage:** 1.0
|
||||||
> **Amount:** `26.64180098`
|
> **Amount:** `26.64180098`
|
||||||
> **Enter Tag:** Awesome Long Signal
|
> **Enter Tag:** Awesome Long Signal
|
||||||
> **Open Rate:** `0.00007489`
|
> **Open Rate:** `0.00007489`
|
||||||
> **Current Rate:** `0.00007489`
|
> **Current Rate:** `0.00007489`
|
||||||
> **Unrealized Profit:** `12.95%`
|
> **Unrealized Profit:** `12.95%`
|
||||||
> **Stoploss:** `0.00007389 (-0.02%)`
|
> **Stoploss:** `0.00007389 (-0.02%)`
|
||||||
|
|
||||||
### /status table
|
### /status table
|
||||||
|
|
||||||
@@ -274,34 +297,34 @@ current max
|
|||||||
|
|
||||||
Return a summary of your profit/loss and performance.
|
Return a summary of your profit/loss and performance.
|
||||||
|
|
||||||
> **ROI:** Close trades
|
> **ROI:** Close trades
|
||||||
> ∙ `0.00485701 BTC (2.2%) (15.2 Σ%)`
|
> ∙ `0.00485701 BTC (2.2%) (15.2 Σ%)`
|
||||||
> ∙ `62.968 USD`
|
> ∙ `62.968 USD`
|
||||||
> **ROI:** All trades
|
> **ROI:** All trades
|
||||||
> ∙ `0.00255280 BTC (1.5%) (6.43 Σ%)`
|
> ∙ `0.00255280 BTC (1.5%) (6.43 Σ%)`
|
||||||
> ∙ `33.095 EUR`
|
> ∙ `33.095 EUR`
|
||||||
>
|
>
|
||||||
> **Total Trade Count:** `138`
|
> **Total Trade Count:** `138`
|
||||||
> **Bot started:** `2022-07-11 18:40:44`
|
> **Bot started:** `2022-07-11 18:40:44`
|
||||||
> **First Trade opened:** `3 days ago`
|
> **First Trade opened:** `3 days ago`
|
||||||
> **Latest Trade opened:** `2 minutes ago`
|
> **Latest Trade opened:** `2 minutes ago`
|
||||||
> **Avg. Duration:** `2:33:45`
|
> **Avg. Duration:** `2:33:45`
|
||||||
> **Best Performing:** `PAY/BTC: 50.23%`
|
> **Best Performing:** `PAY/BTC: 50.23%`
|
||||||
> **Trading volume:** `0.5 BTC`
|
> **Trading volume:** `0.5 BTC`
|
||||||
> **Profit factor:** `1.04`
|
> **Profit factor:** `1.04`
|
||||||
> **Win / Loss:** `102 / 36`
|
> **Win / Loss:** `102 / 36`
|
||||||
> **Winrate:** `73.91%`
|
> **Winrate:** `73.91%`
|
||||||
> **Expectancy (Ratio):** `4.87 (1.66)`
|
> **Expectancy (Ratio):** `4.87 (1.66)`
|
||||||
> **Max Drawdown:** `9.23% (0.01255 BTC)`
|
> **Max Drawdown:** `9.23% (0.01255 BTC)`
|
||||||
|
|
||||||
The relative profit of `1.2%` is the average profit per trade.
|
The relative profit of `1.2%` is the average profit per trade.
|
||||||
The relative profit of `15.2 Σ%` is be based on the starting capital - so in this case, the starting capital was `0.00485701 * 1.152 = 0.00738 BTC`.
|
The relative profit of `15.2 Σ%` is be based on the starting capital - so in this case, the starting capital was `0.00485701 * 1.152 = 0.00738 BTC`.
|
||||||
Starting capital is either taken from the `available_capital` setting, or calculated by using current wallet size - profits.
|
**Starting capital(**) is either taken from the `available_capital` setting, or calculated by using current wallet size - profits.
|
||||||
Profit Factor is calculated as gross profits / gross losses - and should serve as an overall metric for the strategy.
|
**Profit Factor** is calculated as gross profits / gross losses - and should serve as an overall metric for the strategy.
|
||||||
Expectancy corresponds to the average return per currency unit at risk, i.e. the winrate and the risk-reward ratio (the average gain of winning trades compared to the average loss of losing trades).
|
**Expectancy** corresponds to the average return per currency unit at risk, i.e. the winrate and the risk-reward ratio (the average gain of winning trades compared to the average loss of losing trades).
|
||||||
Expectancy Ratio is expected profit or loss of a subsequent trade based on the performance of all past trades.
|
**Expectancy Ratio** is expected profit or loss of a subsequent trade based on the performance of all past trades.
|
||||||
Max drawdown corresponds to the backtesting metric `Absolute Drawdown (Account)` - calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`.
|
**Max drawdown** corresponds to the backtesting metric `Absolute Drawdown (Account)` - calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`.
|
||||||
Bot started date will refer to the date the bot was first started. For older bots, this will default to the first trade's open date.
|
**Bot started date** will refer to the date the bot was first started. For older bots, this will default to the first trade's open date.
|
||||||
|
|
||||||
### /forceexit <trade_id>
|
### /forceexit <trade_id>
|
||||||
|
|
||||||
@@ -329,33 +352,34 @@ Note that for this to work, `force_entry_enable` needs to be set to true.
|
|||||||
### /performance
|
### /performance
|
||||||
|
|
||||||
Return the performance of each crypto-currency the bot has sold.
|
Return the performance of each crypto-currency the bot has sold.
|
||||||
> Performance:
|
> Performance:
|
||||||
> 1. `RCN/BTC 0.003 BTC (57.77%) (1)`
|
> 1. `RCN/BTC 0.003 BTC (57.77%) (1)`
|
||||||
> 2. `PAY/BTC 0.0012 BTC (56.91%) (1)`
|
> 2. `PAY/BTC 0.0012 BTC (56.91%) (1)`
|
||||||
> 3. `VIB/BTC 0.0011 BTC (47.07%) (1)`
|
> 3. `VIB/BTC 0.0011 BTC (47.07%) (1)`
|
||||||
> 4. `SALT/BTC 0.0010 BTC (30.24%) (1)`
|
> 4. `SALT/BTC 0.0010 BTC (30.24%) (1)`
|
||||||
> 5. `STORJ/BTC 0.0009 BTC (27.24%) (1)`
|
> 5. `STORJ/BTC 0.0009 BTC (27.24%) (1)`
|
||||||
> ...
|
> ...
|
||||||
|
|
||||||
### /balance
|
### /balance
|
||||||
|
|
||||||
Return the balance of all crypto-currency your have on the exchange.
|
Return the balance of all crypto-currency your have on the exchange.
|
||||||
|
|
||||||
> **Currency:** BTC
|
> **Currency:** BTC
|
||||||
> **Available:** 3.05890234
|
> **Available:** 3.05890234
|
||||||
> **Balance:** 3.05890234
|
> **Balance:** 3.05890234
|
||||||
> **Pending:** 0.0
|
> **Pending:** 0.0
|
||||||
|
>
|
||||||
> **Currency:** CVC
|
> **Currency:** CVC
|
||||||
> **Available:** 86.64180098
|
> **Available:** 86.64180098
|
||||||
> **Balance:** 86.64180098
|
> **Balance:** 86.64180098
|
||||||
> **Pending:** 0.0
|
> **Pending:** 0.0
|
||||||
|
|
||||||
### /daily <n>
|
### /daily <n>
|
||||||
|
|
||||||
Per default `/daily` will return the 7 last days. The example below if for `/daily 3`:
|
Per default `/daily` will return the 7 last days. The example below if for `/daily 3`:
|
||||||
|
|
||||||
> **Daily Profit over the last 3 days:**
|
> **Daily Profit over the last 3 days:**
|
||||||
|
|
||||||
```
|
```
|
||||||
Day (count) USDT USD Profit %
|
Day (count) USDT USD Profit %
|
||||||
-------------- ------------ ---------- ----------
|
-------------- ------------ ---------- ----------
|
||||||
@@ -370,6 +394,7 @@ Per default `/weekly` will return the 8 last weeks, including the current week.
|
|||||||
from Monday. The example below if for `/weekly 3`:
|
from Monday. The example below if for `/weekly 3`:
|
||||||
|
|
||||||
> **Weekly Profit over the last 3 weeks (starting from Monday):**
|
> **Weekly Profit over the last 3 weeks (starting from Monday):**
|
||||||
|
|
||||||
```
|
```
|
||||||
Monday (count) Profit BTC Profit USD Profit %
|
Monday (count) Profit BTC Profit USD Profit %
|
||||||
------------- -------------- ------------ ----------
|
------------- -------------- ------------ ----------
|
||||||
@@ -396,18 +421,18 @@ Month (count) Profit BTC Profit USD Profit %
|
|||||||
|
|
||||||
Shows the current whitelist
|
Shows the current whitelist
|
||||||
|
|
||||||
> Using whitelist `StaticPairList` with 22 pairs
|
> Using whitelist `StaticPairList` with 22 pairs
|
||||||
> `IOTA/BTC, NEO/BTC, TRX/BTC, VET/BTC, ADA/BTC, ETC/BTC, NCASH/BTC, DASH/BTC, XRP/BTC, XVG/BTC, EOS/BTC, LTC/BTC, OMG/BTC, BTG/BTC, LSK/BTC, ZEC/BTC, HOT/BTC, IOTX/BTC, XMR/BTC, AST/BTC, XLM/BTC, NANO/BTC`
|
> `IOTA/BTC, NEO/BTC, TRX/BTC, VET/BTC, ADA/BTC, ETC/BTC, NCASH/BTC, DASH/BTC, XRP/BTC, XVG/BTC, EOS/BTC, LTC/BTC, OMG/BTC, BTG/BTC, LSK/BTC, ZEC/BTC, HOT/BTC, IOTX/BTC, XMR/BTC, AST/BTC, XLM/BTC, NANO/BTC`
|
||||||
|
|
||||||
### /blacklist [pair]
|
### /blacklist [pair]
|
||||||
|
|
||||||
Shows the current blacklist.
|
Shows the current blacklist.
|
||||||
If Pair is set, then this pair will be added to the pairlist.
|
If Pair is set, then this pair will be added to the pairlist.
|
||||||
Also supports multiple pairs, separated by a space.
|
Also supports multiple pairs, separated by a space.
|
||||||
Use `/reload_config` to reset the blacklist.
|
Use `/reload_config` to reset the blacklist.
|
||||||
|
|
||||||
> Using blacklist `StaticPairList` with 2 pairs
|
> Using blacklist `StaticPairList` with 2 pairs
|
||||||
>`DODGE/BTC`, `HOT/BTC`.
|
>`DODGE/BTC`, `HOT/BTC`.
|
||||||
|
|
||||||
### /edge
|
### /edge
|
||||||
|
|
||||||
|
|||||||
+19
-18
@@ -18,7 +18,7 @@ The following attributes / properties are available for each individual trade -
|
|||||||
| `open_rate` | float | Rate this trade was entered at (Avg. entry rate in case of trade-adjustments). |
|
| `open_rate` | float | Rate this trade was entered at (Avg. entry rate in case of trade-adjustments). |
|
||||||
| `close_rate` | float | Close rate - only set when is_open = False. |
|
| `close_rate` | float | Close rate - only set when is_open = False. |
|
||||||
| `stake_amount` | float | Amount in Stake (or Quote) currency. |
|
| `stake_amount` | float | Amount in Stake (or Quote) currency. |
|
||||||
| `amount` | float | Amount in Asset / Base currency that is currently owned. |
|
| `amount` | float | Amount in Asset / Base currency that is currently owned. Will be 0.0 until the initial order fills. |
|
||||||
| `open_date` | datetime | Timestamp when trade was opened **use `open_date_utc` instead** |
|
| `open_date` | datetime | Timestamp when trade was opened **use `open_date_utc` instead** |
|
||||||
| `open_date_utc` | datetime | Timestamp when trade was opened - in UTC. |
|
| `open_date_utc` | datetime | Timestamp when trade was opened - in UTC. |
|
||||||
| `close_date` | datetime | Timestamp when trade was closed **use `close_date_utc` instead** |
|
| `close_date` | datetime | Timestamp when trade was closed **use `close_date_utc` instead** |
|
||||||
@@ -130,20 +130,21 @@ Most properties here can be None as they are dependent on the exchange response.
|
|||||||
|
|
||||||
| Attribute | DataType | Description |
|
| Attribute | DataType | Description |
|
||||||
|------------|-------------|-------------|
|
|------------|-------------|-------------|
|
||||||
`trade` | Trade | Trade object this order is attached to
|
| `trade` | Trade | Trade object this order is attached to |
|
||||||
`ft_pair` | string | Pair this order is for
|
| `ft_pair` | string | Pair this order is for |
|
||||||
`ft_is_open` | boolean | is the order filled?
|
| `ft_is_open` | boolean | is the order filled? |
|
||||||
`order_type` | string | Order type as defined on the exchange - usually market, limit or stoploss
|
| `order_type` | string | Order type as defined on the exchange - usually market, limit or stoploss |
|
||||||
`status` | string | Status as defined by ccxt. Usually open, closed, expired or canceled
|
| `status` | string | Status as defined by ccxt. Usually open, closed, expired or canceled |
|
||||||
`side` | string | Buy or Sell
|
| `side` | string | Buy or Sell |
|
||||||
`price` | float | Price the order was placed at
|
| `price` | float | Price the order was placed at |
|
||||||
`average` | float | Average price the order filled at
|
| `average` | float | Average price the order filled at |
|
||||||
`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 dependent 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**
|
| `stake_amount_filled` | float | Filled Stake amount used for this order. *Added in 2024.11.* |
|
||||||
`order_date_utc` | datetime | Order creation date (in UTC)
|
| `order_date` | datetime | Order creation date **use `order_date_utc` instead** |
|
||||||
`order_fill_date` | datetime | Order fill date **use `order_fill_utc` instead**
|
| `order_date_utc` | datetime | Order creation date (in UTC) |
|
||||||
`order_fill_date_utc` | datetime | Order fill date
|
| `order_fill_date` | datetime | Order fill date **use `order_fill_utc` instead** |
|
||||||
|
| `order_fill_date_utc` | datetime | Order fill date |
|
||||||
|
|||||||
+42
-2
@@ -216,6 +216,45 @@ Example: Search dedicated strategy path.
|
|||||||
freqtrade list-strategies --strategy-path ~/.freqtrade/strategies/
|
freqtrade list-strategies --strategy-path ~/.freqtrade/strategies/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## List Hyperopt-Loss functions
|
||||||
|
|
||||||
|
Use the `list-hyperoptloss` subcommand to see all hyperopt loss functions available.
|
||||||
|
|
||||||
|
It provides a quick list of all available loss functions in your environment.
|
||||||
|
|
||||||
|
This subcommand can be useful for finding problems in your environment with loading loss functions: modules with Hyperopt-Loss functions that contain errors and failed to load are printed in red (LOAD FAILED), while hyperopt-Loss functions with duplicate names are printed in yellow (DUPLICATE NAME).
|
||||||
|
|
||||||
|
```
|
||||||
|
usage: freqtrade list-hyperoptloss [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
||||||
|
[-d PATH] [--userdir PATH]
|
||||||
|
[--hyperopt-path PATH] [-1] [--no-color]
|
||||||
|
|
||||||
|
options:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
--hyperopt-path PATH Specify additional lookup path for Hyperopt Loss
|
||||||
|
functions.
|
||||||
|
-1, --one-column Print output in one column.
|
||||||
|
--no-color Disable colorization of hyperopt results. May be
|
||||||
|
useful if you are redirecting output to a file.
|
||||||
|
|
||||||
|
Common arguments:
|
||||||
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
|
--logfile FILE, --log-file FILE
|
||||||
|
Log to the file specified. Special values are:
|
||||||
|
'syslog', 'journald'. See the documentation for more
|
||||||
|
details.
|
||||||
|
-V, --version show program's version number and exit
|
||||||
|
-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.
|
||||||
|
-d PATH, --datadir PATH, --data-dir PATH
|
||||||
|
Path to directory with historical backtesting data.
|
||||||
|
--userdir PATH, --user-data-dir PATH
|
||||||
|
Path to userdata directory.
|
||||||
|
```
|
||||||
|
|
||||||
## List freqAI models
|
## List freqAI models
|
||||||
|
|
||||||
Use the `list-freqaimodels` subcommand to see all freqAI models available.
|
Use the `list-freqaimodels` subcommand to see all freqAI models available.
|
||||||
@@ -418,8 +457,9 @@ Common arguments:
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, only active pairs/markets are shown. Active pairs/markets are those that can currently be traded
|
By default, only active pairs/markets are shown. Active pairs/markets are those that can currently be traded on the exchange.
|
||||||
on the exchange. The see the list of all pairs/markets (not only the active ones), use the `-a`/`-all` option.
|
You can use the `-a`/`-all` option to see the list of all pairs/markets, including the inactive ones.
|
||||||
|
Pairs may be listed as untradeable if the smallest tradeable price for the market is very small, i.e. less than `1e-11` (`0.00000000001`)
|
||||||
|
|
||||||
Pairs/markets are sorted by its symbol string in the printed output.
|
Pairs/markets are sorted by its symbol string in the printed output.
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ We **strongly** recommend that Windows users use [Docker](docker_quickstart.md)
|
|||||||
If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work.
|
If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work.
|
||||||
Otherwise, please follow the instructions below.
|
Otherwise, please follow the instructions below.
|
||||||
|
|
||||||
All instructions assume that python 3.9+ is installed and available.
|
All instructions assume that python 3.10+ is installed and available.
|
||||||
|
|
||||||
## Clone the git repository
|
## Clone the git repository
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ cd freqtrade
|
|||||||
|
|
||||||
Install ta-lib according to the [ta-lib documentation](https://github.com/TA-Lib/ta-lib-python#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, 3.11 and 3.12) 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.10, 3.11 and 3.12) 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.
|
||||||
|
|
||||||
Other versions must be downloaded from the above link.
|
Other versions must be downloaded from the above link.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Freqtrade bot"""
|
"""Freqtrade bot"""
|
||||||
|
|
||||||
__version__ = "2024.7.1"
|
__version__ = "2024.12"
|
||||||
|
|
||||||
if "dev" in __version__:
|
if "dev" in __version__:
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
__main__.py for Freqtrade
|
__main__.py for Freqtrade
|
||||||
To launch Freqtrade as a module
|
To launch Freqtrade as a module
|
||||||
|
|
||||||
> python -m freqtrade (with Python >= 3.9)
|
> python -m freqtrade (with Python >= 3.10)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from freqtrade import main
|
from freqtrade import main
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from freqtrade.commands.data_commands import (
|
|||||||
start_convert_trades,
|
start_convert_trades,
|
||||||
start_download_data,
|
start_download_data,
|
||||||
start_list_data,
|
start_list_data,
|
||||||
|
start_list_trades_data,
|
||||||
)
|
)
|
||||||
from freqtrade.commands.db_commands import start_convert_db
|
from freqtrade.commands.db_commands import start_convert_db
|
||||||
from freqtrade.commands.deploy_commands import (
|
from freqtrade.commands.deploy_commands import (
|
||||||
@@ -26,6 +27,7 @@ from freqtrade.commands.hyperopt_commands import start_hyperopt_list, start_hype
|
|||||||
from freqtrade.commands.list_commands import (
|
from freqtrade.commands.list_commands import (
|
||||||
start_list_exchanges,
|
start_list_exchanges,
|
||||||
start_list_freqAI_models,
|
start_list_freqAI_models,
|
||||||
|
start_list_hyperopt_loss_functions,
|
||||||
start_list_markets,
|
start_list_markets,
|
||||||
start_list_strategies,
|
start_list_strategies,
|
||||||
start_list_timeframes,
|
start_list_timeframes,
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from freqtrade.configuration import setup_utils_configuration
|
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import ConfigurationError, OperationalException
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||||
|
|
||||||
@@ -10,13 +9,15 @@ from freqtrade.exceptions import ConfigurationError, OperationalException
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def setup_analyze_configuration(args: Dict[str, Any], method: RunMode) -> Dict[str, Any]:
|
def setup_analyze_configuration(args: dict[str, Any], method: RunMode) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Prepare the configuration for the entry/exit reason analysis module
|
Prepare the configuration for the entry/exit reason analysis module
|
||||||
:param args: Cli args from Arguments()
|
:param args: Cli args from Arguments()
|
||||||
:param method: Bot running mode
|
:param method: Bot running mode
|
||||||
:return: Configuration
|
:return: Configuration
|
||||||
"""
|
"""
|
||||||
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
|
|
||||||
config = setup_utils_configuration(args, method)
|
config = setup_utils_configuration(args, method)
|
||||||
|
|
||||||
no_unlimited_runmodes = {
|
no_unlimited_runmodes = {
|
||||||
@@ -47,7 +48,7 @@ def setup_analyze_configuration(args: Dict[str, Any], method: RunMode) -> Dict[s
|
|||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
def start_analysis_entries_exits(args: Dict[str, Any]) -> None:
|
def start_analysis_entries_exits(args: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Start analysis script
|
Start analysis script
|
||||||
:param args: Cli args from Arguments()
|
:param args: Cli args from Arguments()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ This module contains the argument manager class
|
|||||||
from argparse import ArgumentParser, Namespace, _ArgumentGroup
|
from argparse import ArgumentParser, Namespace, _ArgumentGroup
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Optional, Union
|
from typing import Any
|
||||||
|
|
||||||
from freqtrade.commands.cli_options import AVAILABLE_CLI_OPTIONS
|
from freqtrade.commands.cli_options import AVAILABLE_CLI_OPTIONS
|
||||||
from freqtrade.constants import DEFAULT_CONFIG
|
from freqtrade.constants import DEFAULT_CONFIG
|
||||||
@@ -23,7 +23,7 @@ ARGS_STRATEGY = [
|
|||||||
|
|
||||||
ARGS_TRADE = ["db_url", "sd_notify", "dry_run", "dry_run_wallet", "fee"]
|
ARGS_TRADE = ["db_url", "sd_notify", "dry_run", "dry_run_wallet", "fee"]
|
||||||
|
|
||||||
ARGS_WEBSERVER: List[str] = []
|
ARGS_WEBSERVER: list[str] = []
|
||||||
|
|
||||||
ARGS_COMMON_OPTIMIZE = [
|
ARGS_COMMON_OPTIMIZE = [
|
||||||
"timeframe",
|
"timeframe",
|
||||||
@@ -37,7 +37,6 @@ ARGS_COMMON_OPTIMIZE = [
|
|||||||
|
|
||||||
ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + [
|
ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + [
|
||||||
"position_stacking",
|
"position_stacking",
|
||||||
"use_max_market_positions",
|
|
||||||
"enable_protections",
|
"enable_protections",
|
||||||
"dry_run_wallet",
|
"dry_run_wallet",
|
||||||
"timeframe_detail",
|
"timeframe_detail",
|
||||||
@@ -53,7 +52,6 @@ ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + [
|
|||||||
"hyperopt",
|
"hyperopt",
|
||||||
"hyperopt_path",
|
"hyperopt_path",
|
||||||
"position_stacking",
|
"position_stacking",
|
||||||
"use_max_market_positions",
|
|
||||||
"enable_protections",
|
"enable_protections",
|
||||||
"dry_run_wallet",
|
"dry_run_wallet",
|
||||||
"timeframe_detail",
|
"timeframe_detail",
|
||||||
@@ -117,7 +115,7 @@ 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_SHOW_CONFIG = ["user_data_dir", "config", "show_sensitive"]
|
||||||
|
|
||||||
ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"]
|
ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "strategy_path", "template"]
|
||||||
|
|
||||||
ARGS_CONVERT_DATA_TRADES = ["pairs", "format_from_trades", "format_to", "erase", "exchange"]
|
ARGS_CONVERT_DATA_TRADES = ["pairs", "format_from_trades", "format_to", "erase", "exchange"]
|
||||||
ARGS_CONVERT_DATA = ["pairs", "format_from", "format_to", "erase", "exchange"]
|
ARGS_CONVERT_DATA = ["pairs", "format_from", "format_to", "erase", "exchange"]
|
||||||
@@ -132,7 +130,15 @@ ARGS_CONVERT_TRADES = [
|
|||||||
"trading_mode",
|
"trading_mode",
|
||||||
]
|
]
|
||||||
|
|
||||||
ARGS_LIST_DATA = ["exchange", "dataformat_ohlcv", "pairs", "trading_mode", "show_timerange"]
|
ARGS_LIST_DATA = [
|
||||||
|
"exchange",
|
||||||
|
"dataformat_ohlcv",
|
||||||
|
"dataformat_trades",
|
||||||
|
"trades",
|
||||||
|
"pairs",
|
||||||
|
"trading_mode",
|
||||||
|
"show_timerange",
|
||||||
|
]
|
||||||
|
|
||||||
ARGS_DOWNLOAD_DATA = [
|
ARGS_DOWNLOAD_DATA = [
|
||||||
"pairs",
|
"pairs",
|
||||||
@@ -220,6 +226,8 @@ ARGS_ANALYZE_ENTRIES_EXITS = [
|
|||||||
"enter_reason_list",
|
"enter_reason_list",
|
||||||
"exit_reason_list",
|
"exit_reason_list",
|
||||||
"indicator_list",
|
"indicator_list",
|
||||||
|
"entry_only",
|
||||||
|
"exit_only",
|
||||||
"timerange",
|
"timerange",
|
||||||
"analysis_rejected",
|
"analysis_rejected",
|
||||||
"analysis_to_csv",
|
"analysis_to_csv",
|
||||||
@@ -232,8 +240,7 @@ ARGS_STRATEGY_UPDATER = ["strategy_list", "strategy_path", "recursive_strategy_s
|
|||||||
ARGS_LOOKAHEAD_ANALYSIS = [
|
ARGS_LOOKAHEAD_ANALYSIS = [
|
||||||
a
|
a
|
||||||
for a in ARGS_BACKTEST
|
for a in ARGS_BACKTEST
|
||||||
if a
|
if a not in ("position_stacking", "backtest_cache", "backtest_breakdown")
|
||||||
not in ("position_stacking", "use_max_market_positions", "backtest_cache", "backtest_breakdown")
|
|
||||||
] + ["minimum_trade_amount", "targeted_trade_amount", "lookahead_analysis_exportfilename"]
|
] + ["minimum_trade_amount", "targeted_trade_amount", "lookahead_analysis_exportfilename"]
|
||||||
|
|
||||||
ARGS_RECURSIVE_ANALYSIS = ["timeframe", "timerange", "dataformat_ohlcv", "pairs", "startup_candle"]
|
ARGS_RECURSIVE_ANALYSIS = ["timeframe", "timerange", "dataformat_ohlcv", "pairs", "startup_candle"]
|
||||||
@@ -248,6 +255,7 @@ NO_CONF_REQURIED = [
|
|||||||
"list-pairs",
|
"list-pairs",
|
||||||
"list-strategies",
|
"list-strategies",
|
||||||
"list-freqaimodels",
|
"list-freqaimodels",
|
||||||
|
"list-hyperoptloss",
|
||||||
"list-data",
|
"list-data",
|
||||||
"hyperopt-list",
|
"hyperopt-list",
|
||||||
"hyperopt-show",
|
"hyperopt-show",
|
||||||
@@ -267,11 +275,11 @@ class Arguments:
|
|||||||
Arguments Class. Manage the arguments received by the cli
|
Arguments Class. Manage the arguments received by the cli
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, args: Optional[List[str]]) -> None:
|
def __init__(self, args: list[str] | None) -> None:
|
||||||
self.args = args
|
self.args = args
|
||||||
self._parsed_arg: Optional[Namespace] = None
|
self._parsed_arg: Namespace | None = None
|
||||||
|
|
||||||
def get_parsed_arg(self) -> Dict[str, Any]:
|
def get_parsed_arg(self) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Return the list of arguments
|
Return the list of arguments
|
||||||
:return: List[str] List of arguments
|
:return: List[str] List of arguments
|
||||||
@@ -311,9 +319,7 @@ class Arguments:
|
|||||||
|
|
||||||
return parsed_arg
|
return parsed_arg
|
||||||
|
|
||||||
def _build_args(
|
def _build_args(self, optionlist: list[str], parser: ArgumentParser | _ArgumentGroup) -> None:
|
||||||
self, optionlist: List[str], parser: Union[ArgumentParser, _ArgumentGroup]
|
|
||||||
) -> None:
|
|
||||||
for val in optionlist:
|
for val in optionlist:
|
||||||
opt = AVAILABLE_CLI_OPTIONS[val]
|
opt = AVAILABLE_CLI_OPTIONS[val]
|
||||||
parser.add_argument(*opt.cli, dest=val, **opt.kwargs)
|
parser.add_argument(*opt.cli, dest=val, **opt.kwargs)
|
||||||
@@ -355,6 +361,7 @@ class Arguments:
|
|||||||
start_list_data,
|
start_list_data,
|
||||||
start_list_exchanges,
|
start_list_exchanges,
|
||||||
start_list_freqAI_models,
|
start_list_freqAI_models,
|
||||||
|
start_list_hyperopt_loss_functions,
|
||||||
start_list_markets,
|
start_list_markets,
|
||||||
start_list_strategies,
|
start_list_strategies,
|
||||||
start_list_timeframes,
|
start_list_timeframes,
|
||||||
@@ -556,6 +563,15 @@ class Arguments:
|
|||||||
list_strategies_cmd.set_defaults(func=start_list_strategies)
|
list_strategies_cmd.set_defaults(func=start_list_strategies)
|
||||||
self._build_args(optionlist=ARGS_LIST_STRATEGIES, parser=list_strategies_cmd)
|
self._build_args(optionlist=ARGS_LIST_STRATEGIES, parser=list_strategies_cmd)
|
||||||
|
|
||||||
|
# Add list-Hyperopt loss subcommand
|
||||||
|
list_hyperopt_loss_cmd = subparsers.add_parser(
|
||||||
|
"list-hyperoptloss",
|
||||||
|
help="Print available hyperopt loss functions.",
|
||||||
|
parents=[_common_parser],
|
||||||
|
)
|
||||||
|
list_hyperopt_loss_cmd.set_defaults(func=start_list_hyperopt_loss_functions)
|
||||||
|
self._build_args(optionlist=ARGS_LIST_HYPEROPTS, parser=list_hyperopt_loss_cmd)
|
||||||
|
|
||||||
# Add list-freqAI Models subcommand
|
# Add list-freqAI Models subcommand
|
||||||
list_freqaimodels_cmd = subparsers.add_parser(
|
list_freqaimodels_cmd = subparsers.add_parser(
|
||||||
"list-freqaimodels",
|
"list-freqaimodels",
|
||||||
|
|||||||
@@ -1,261 +1,27 @@
|
|||||||
import logging
|
import logging
|
||||||
import secrets
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
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.directory_operations import chown_user_directory
|
|
||||||
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
|
|
||||||
from freqtrade.enums import RunMode
|
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.util import render_template
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def validate_is_int(val):
|
def start_new_config(args: dict[str, Any]) -> None:
|
||||||
try:
|
|
||||||
_ = int(val)
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def validate_is_float(val):
|
|
||||||
try:
|
|
||||||
_ = float(val)
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def ask_user_overwrite(config_path: Path) -> bool:
|
|
||||||
questions = [
|
|
||||||
{
|
|
||||||
"type": "confirm",
|
|
||||||
"name": "overwrite",
|
|
||||||
"message": f"File {config_path} already exists. Overwrite?",
|
|
||||||
"default": False,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
answers = prompt(questions)
|
|
||||||
return answers["overwrite"]
|
|
||||||
|
|
||||||
|
|
||||||
def ask_user_config() -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Ask user a few questions to build the configuration.
|
|
||||||
Interactive questions built using https://github.com/tmbo/questionary
|
|
||||||
:returns: Dict with keys to put into template
|
|
||||||
"""
|
|
||||||
questions: List[Dict[str, Any]] = [
|
|
||||||
{
|
|
||||||
"type": "confirm",
|
|
||||||
"name": "dry_run",
|
|
||||||
"message": "Do you want to enable Dry-run (simulated trades)?",
|
|
||||||
"default": True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"name": "stake_currency",
|
|
||||||
"message": "Please insert your stake currency:",
|
|
||||||
"default": "USDT",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"name": "stake_amount",
|
|
||||||
"message": f"Please insert your stake amount (Number or '{UNLIMITED_STAKE_AMOUNT}'):",
|
|
||||||
"default": "unlimited",
|
|
||||||
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val),
|
|
||||||
"filter": lambda val: (
|
|
||||||
'"' + UNLIMITED_STAKE_AMOUNT + '"' if val == UNLIMITED_STAKE_AMOUNT else val
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"name": "max_open_trades",
|
|
||||||
"message": "Please insert max_open_trades (Integer or -1 for unlimited open trades):",
|
|
||||||
"default": "3",
|
|
||||||
"validate": lambda val: validate_is_int(val),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "select",
|
|
||||||
"name": "timeframe_in_config",
|
|
||||||
"message": "Time",
|
|
||||||
"choices": ["Have the strategy define timeframe.", "Override in configuration."],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"name": "timeframe",
|
|
||||||
"message": "Please insert your desired timeframe (e.g. 5m):",
|
|
||||||
"default": "5m",
|
|
||||||
"when": lambda x: x["timeframe_in_config"] == "Override in configuration.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"name": "fiat_display_currency",
|
|
||||||
"message": (
|
|
||||||
"Please insert your display Currency for reporting "
|
|
||||||
"(leave empty to disable FIAT conversion):"
|
|
||||||
),
|
|
||||||
"default": "USD",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "select",
|
|
||||||
"name": "exchange_name",
|
|
||||||
"message": "Select exchange",
|
|
||||||
"choices": [
|
|
||||||
"binance",
|
|
||||||
"binanceus",
|
|
||||||
"bingx",
|
|
||||||
"gate",
|
|
||||||
"htx",
|
|
||||||
"kraken",
|
|
||||||
"kucoin",
|
|
||||||
"okx",
|
|
||||||
Separator("------------------"),
|
|
||||||
"other",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "confirm",
|
|
||||||
"name": "trading_mode",
|
|
||||||
"message": "Do you want to trade Perpetual Swaps (perpetual futures)?",
|
|
||||||
"default": False,
|
|
||||||
"filter": lambda val: "futures" if val else "spot",
|
|
||||||
"when": lambda x: x["exchange_name"] in ["binance", "gate", "okx", "bybit"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "autocomplete",
|
|
||||||
"name": "exchange_name",
|
|
||||||
"message": "Type your exchange name (Must be supported by ccxt)",
|
|
||||||
"choices": available_exchanges(),
|
|
||||||
"when": lambda x: x["exchange_name"] == "other",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "password",
|
|
||||||
"name": "exchange_key",
|
|
||||||
"message": "Insert Exchange Key",
|
|
||||||
"when": lambda x: not x["dry_run"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "password",
|
|
||||||
"name": "exchange_secret",
|
|
||||||
"message": "Insert Exchange Secret",
|
|
||||||
"when": lambda x: not x["dry_run"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "password",
|
|
||||||
"name": "exchange_key_password",
|
|
||||||
"message": "Insert Exchange API Key password",
|
|
||||||
"when": lambda x: not x["dry_run"] and x["exchange_name"] in ("kucoin", "okx"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "confirm",
|
|
||||||
"name": "telegram",
|
|
||||||
"message": "Do you want to enable Telegram?",
|
|
||||||
"default": False,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "password",
|
|
||||||
"name": "telegram_token",
|
|
||||||
"message": "Insert Telegram token",
|
|
||||||
"when": lambda x: x["telegram"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "password",
|
|
||||||
"name": "telegram_chat_id",
|
|
||||||
"message": "Insert Telegram chat id",
|
|
||||||
"when": lambda x: x["telegram"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "confirm",
|
|
||||||
"name": "api_server",
|
|
||||||
"message": "Do you want to enable the Rest API (includes FreqUI)?",
|
|
||||||
"default": False,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"name": "api_server_listen_addr",
|
|
||||||
"message": (
|
|
||||||
"Insert Api server Listen Address (0.0.0.0 for docker, "
|
|
||||||
"otherwise best left untouched)"
|
|
||||||
),
|
|
||||||
"default": "127.0.0.1" if not running_in_docker() else "0.0.0.0", # noqa: S104
|
|
||||||
"when": lambda x: x["api_server"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"name": "api_server_username",
|
|
||||||
"message": "Insert api-server username",
|
|
||||||
"default": "freqtrader",
|
|
||||||
"when": lambda x: x["api_server"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "password",
|
|
||||||
"name": "api_server_password",
|
|
||||||
"message": "Insert api-server password",
|
|
||||||
"when": lambda x: x["api_server"],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
answers = prompt(questions)
|
|
||||||
|
|
||||||
if not answers:
|
|
||||||
# Interrupted questionary sessions return an empty dict.
|
|
||||||
raise OperationalException("User interrupted interactive questions.")
|
|
||||||
# Ensure default is set for non-futures exchanges
|
|
||||||
answers["trading_mode"] = answers.get("trading_mode", "spot")
|
|
||||||
answers["margin_mode"] = "isolated" if answers.get("trading_mode") == "futures" else ""
|
|
||||||
# Force JWT token to be a random string
|
|
||||||
answers["api_server_jwt_key"] = secrets.token_hex()
|
|
||||||
answers["api_server_ws_token"] = secrets.token_urlsafe(25)
|
|
||||||
|
|
||||||
return answers
|
|
||||||
|
|
||||||
|
|
||||||
def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None:
|
|
||||||
"""
|
|
||||||
Applies selections to the template and writes the result to config_path
|
|
||||||
:param config_path: Path object for new config file. Should not exist yet
|
|
||||||
:param selections: Dict containing selections taken by the user.
|
|
||||||
"""
|
|
||||||
from jinja2.exceptions import TemplateNotFound
|
|
||||||
|
|
||||||
try:
|
|
||||||
exchange_template = MAP_EXCHANGE_CHILDCLASS.get(
|
|
||||||
selections["exchange_name"], selections["exchange_name"]
|
|
||||||
)
|
|
||||||
|
|
||||||
selections["exchange"] = render_template(
|
|
||||||
templatefile=f"subtemplates/exchange_{exchange_template}.j2", arguments=selections
|
|
||||||
)
|
|
||||||
except TemplateNotFound:
|
|
||||||
selections["exchange"] = render_template(
|
|
||||||
templatefile="subtemplates/exchange_generic.j2", arguments=selections
|
|
||||||
)
|
|
||||||
|
|
||||||
config_text = render_template(templatefile="base_config.json.j2", arguments=selections)
|
|
||||||
|
|
||||||
logger.info(f"Writing config to `{config_path}`.")
|
|
||||||
logger.info(
|
|
||||||
"Please make sure to check the configuration contents and adjust settings to your needs."
|
|
||||||
)
|
|
||||||
|
|
||||||
config_path.write_text(config_text)
|
|
||||||
|
|
||||||
|
|
||||||
def start_new_config(args: Dict[str, Any]) -> None:
|
|
||||||
"""
|
"""
|
||||||
Create a new strategy from a template
|
Create a new strategy from a template
|
||||||
Asking the user questions to fill out the template accordingly.
|
Asking the user questions to fill out the template accordingly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from freqtrade.configuration.deploy_config import (
|
||||||
|
ask_user_config,
|
||||||
|
ask_user_overwrite,
|
||||||
|
deploy_new_config,
|
||||||
|
)
|
||||||
|
from freqtrade.configuration.directory_operations import chown_user_directory
|
||||||
|
|
||||||
config_path = Path(args["config"][0])
|
config_path = Path(args["config"][0])
|
||||||
chown_user_directory(config_path.parent)
|
chown_user_directory(config_path.parent)
|
||||||
if config_path.exists():
|
if config_path.exists():
|
||||||
@@ -271,10 +37,11 @@ def start_new_config(args: Dict[str, Any]) -> None:
|
|||||||
deploy_new_config(config_path, selections)
|
deploy_new_config(config_path, selections)
|
||||||
|
|
||||||
|
|
||||||
def start_show_config(args: Dict[str, Any]) -> None:
|
def start_show_config(args: dict[str, Any]) -> None:
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE, set_dry=False)
|
from freqtrade.configuration import sanitize_config
|
||||||
|
from freqtrade.configuration.config_setup import setup_utils_configuration
|
||||||
|
|
||||||
# TODO: Sanitize from sensitive info before printing
|
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE, set_dry=False)
|
||||||
|
|
||||||
print("Your combined configuration is:")
|
print("Your combined configuration is:")
|
||||||
config_sanitized = sanitize_config(
|
config_sanitized = sanitize_config(
|
||||||
|
|||||||
@@ -168,14 +168,6 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
default=False,
|
default=False,
|
||||||
),
|
),
|
||||||
"use_max_market_positions": Arg(
|
|
||||||
"--dmmp",
|
|
||||||
"--disable-max-market-positions",
|
|
||||||
help="Disable applying `max_open_trades` during backtest "
|
|
||||||
"(same as setting `max_open_trades` to a very high number).",
|
|
||||||
action="store_false",
|
|
||||||
default=True,
|
|
||||||
),
|
|
||||||
"backtest_show_pair_list": Arg(
|
"backtest_show_pair_list": Arg(
|
||||||
"--show-pair-list",
|
"--show-pair-list",
|
||||||
help="Show backtesting pairlist sorted by profit.",
|
help="Show backtesting pairlist sorted by profit.",
|
||||||
@@ -446,8 +438,12 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
),
|
),
|
||||||
"download_trades": Arg(
|
"download_trades": Arg(
|
||||||
"--dl-trades",
|
"--dl-trades",
|
||||||
help="Download trades instead of OHLCV data. The bot will resample trades to the "
|
help="Download trades instead of OHLCV data.",
|
||||||
"desired timeframe as specified as --timeframes/-t.",
|
action="store_true",
|
||||||
|
),
|
||||||
|
"trades": Arg(
|
||||||
|
"--trades",
|
||||||
|
help="Work on trades data instead of OHLCV data.",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
),
|
),
|
||||||
"convert_trades": Arg(
|
"convert_trades": Arg(
|
||||||
@@ -715,6 +711,12 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
nargs="+",
|
nargs="+",
|
||||||
default=[],
|
default=[],
|
||||||
),
|
),
|
||||||
|
"entry_only": Arg(
|
||||||
|
"--entry-only", help=("Only analyze entry signals."), action="store_true", default=False
|
||||||
|
),
|
||||||
|
"exit_only": Arg(
|
||||||
|
"--exit-only", help=("Only analyze exit signals."), action="store_true", default=False
|
||||||
|
),
|
||||||
"analysis_rejected": Arg(
|
"analysis_rejected": Arg(
|
||||||
"--rejected-signals",
|
"--rejected-signals",
|
||||||
help="Analyse rejected signals",
|
help="Analyse rejected signals",
|
||||||
|
|||||||
@@ -1,23 +1,12 @@
|
|||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from freqtrade.configuration import TimeRange, setup_utils_configuration
|
|
||||||
from freqtrade.constants import DATETIME_PRINT_FORMAT, DL_DATA_TIMEFRAMES, Config
|
from freqtrade.constants import DATETIME_PRINT_FORMAT, DL_DATA_TIMEFRAMES, Config
|
||||||
from freqtrade.data.converter import (
|
|
||||||
convert_ohlcv_format,
|
|
||||||
convert_trades_format,
|
|
||||||
convert_trades_to_ohlcv,
|
|
||||||
)
|
|
||||||
from freqtrade.data.history import download_data_main
|
|
||||||
from freqtrade.enums import CandleType, RunMode, TradingMode
|
from freqtrade.enums import CandleType, RunMode, TradingMode
|
||||||
from freqtrade.exceptions import ConfigurationError
|
from freqtrade.exceptions import ConfigurationError
|
||||||
from freqtrade.exchange import timeframe_to_minutes
|
|
||||||
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist
|
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist
|
||||||
from freqtrade.resolvers import ExchangeResolver
|
|
||||||
from freqtrade.util import print_rich_table
|
|
||||||
from freqtrade.util.migrations import migrate_data
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -37,10 +26,13 @@ def _check_data_config_download_sanity(config: Config) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def start_download_data(args: Dict[str, Any]) -> None:
|
def start_download_data(args: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Download data (former download_backtest_data.py script)
|
Download data (former download_backtest_data.py script)
|
||||||
"""
|
"""
|
||||||
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
|
from freqtrade.data.history import download_data_main
|
||||||
|
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
|
||||||
|
|
||||||
_check_data_config_download_sanity(config)
|
_check_data_config_download_sanity(config)
|
||||||
@@ -52,7 +44,11 @@ def start_download_data(args: Dict[str, Any]) -> None:
|
|||||||
sys.exit("SIGINT received, aborting ...")
|
sys.exit("SIGINT received, aborting ...")
|
||||||
|
|
||||||
|
|
||||||
def start_convert_trades(args: Dict[str, Any]) -> None:
|
def start_convert_trades(args: dict[str, Any]) -> None:
|
||||||
|
from freqtrade.configuration import TimeRange, setup_utils_configuration
|
||||||
|
from freqtrade.data.converter import convert_trades_to_ohlcv
|
||||||
|
from freqtrade.resolvers import ExchangeResolver
|
||||||
|
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
|
||||||
|
|
||||||
timerange = TimeRange()
|
timerange = TimeRange()
|
||||||
@@ -91,10 +87,14 @@ def start_convert_trades(args: Dict[str, Any]) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def start_convert_data(args: Dict[str, Any], ohlcv: bool = True) -> None:
|
def start_convert_data(args: dict[str, Any], ohlcv: bool = True) -> None:
|
||||||
"""
|
"""
|
||||||
Convert data from one format to another
|
Convert data from one format to another
|
||||||
"""
|
"""
|
||||||
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
|
from freqtrade.data.converter import convert_ohlcv_format, convert_trades_format
|
||||||
|
from freqtrade.util.migrations import migrate_data
|
||||||
|
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
if ohlcv:
|
if ohlcv:
|
||||||
migrate_data(config)
|
migrate_data(config)
|
||||||
@@ -113,10 +113,17 @@ def start_convert_data(args: Dict[str, Any], ohlcv: bool = True) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def start_list_data(args: Dict[str, Any]) -> None:
|
def start_list_data(args: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
List available backtest data
|
List available OHLCV data
|
||||||
"""
|
"""
|
||||||
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
|
from freqtrade.util import print_rich_table
|
||||||
|
|
||||||
|
if args["trades"]:
|
||||||
|
start_list_trades_data(args)
|
||||||
|
return
|
||||||
|
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|
||||||
@@ -127,7 +134,6 @@ def start_list_data(args: Dict[str, Any]) -> None:
|
|||||||
paircombs = dhc.ohlcv_get_available_data(
|
paircombs = dhc.ohlcv_get_available_data(
|
||||||
config["datadir"], config.get("trading_mode", TradingMode.SPOT)
|
config["datadir"], config.get("trading_mode", TradingMode.SPOT)
|
||||||
)
|
)
|
||||||
|
|
||||||
if args["pairs"]:
|
if args["pairs"]:
|
||||||
paircombs = [comb for comb in paircombs if comb[0] in args["pairs"]]
|
paircombs = [comb for comb in paircombs if comb[0] in args["pairs"]]
|
||||||
title = f"Found {len(paircombs)} pair / timeframe combinations."
|
title = f"Found {len(paircombs)} pair / timeframe combinations."
|
||||||
@@ -171,3 +177,54 @@ def start_list_data(args: Dict[str, Any]) -> None:
|
|||||||
summary=title,
|
summary=title,
|
||||||
table_kwargs={"min_width": 50},
|
table_kwargs={"min_width": 50},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def start_list_trades_data(args: dict[str, Any]) -> None:
|
||||||
|
"""
|
||||||
|
List available Trades data
|
||||||
|
"""
|
||||||
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
|
from freqtrade.misc import plural
|
||||||
|
from freqtrade.util import print_rich_table
|
||||||
|
|
||||||
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|
||||||
|
from freqtrade.data.history import get_datahandler
|
||||||
|
|
||||||
|
dhc = get_datahandler(config["datadir"], config["dataformat_trades"])
|
||||||
|
|
||||||
|
paircombs = dhc.trades_get_available_data(
|
||||||
|
config["datadir"], config.get("trading_mode", TradingMode.SPOT)
|
||||||
|
)
|
||||||
|
|
||||||
|
if args["pairs"]:
|
||||||
|
paircombs = [comb for comb in paircombs if comb in args["pairs"]]
|
||||||
|
|
||||||
|
title = f"Found trades data for {len(paircombs)} {plural(len(paircombs), 'pair')}."
|
||||||
|
if not config.get("show_timerange"):
|
||||||
|
print_rich_table(
|
||||||
|
[(pair, config.get("candle_type_def", CandleType.SPOT)) for pair in sorted(paircombs)],
|
||||||
|
("Pair", "Type"),
|
||||||
|
title,
|
||||||
|
table_kwargs={"min_width": 50},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
paircombs1 = [
|
||||||
|
(pair, *dhc.trades_data_min_max(pair, config.get("trading_mode", TradingMode.SPOT)))
|
||||||
|
for pair in paircombs
|
||||||
|
]
|
||||||
|
print_rich_table(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
pair,
|
||||||
|
config.get("candle_type_def", CandleType.SPOT),
|
||||||
|
start.strftime(DATETIME_PRINT_FORMAT),
|
||||||
|
end.strftime(DATETIME_PRINT_FORMAT),
|
||||||
|
str(length),
|
||||||
|
)
|
||||||
|
for pair, start, end, length in sorted(paircombs1, key=lambda x: (x[0]))
|
||||||
|
],
|
||||||
|
("Pair", "Type", "From", "To", "Trades"),
|
||||||
|
summary=title,
|
||||||
|
table_kwargs={"min_width": 50},
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from sqlalchemy import func, select
|
|
||||||
|
|
||||||
from freqtrade.configuration.config_setup import setup_utils_configuration
|
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def start_convert_db(args: Dict[str, Any]) -> None:
|
def start_convert_db(args: dict[str, Any]) -> None:
|
||||||
|
from sqlalchemy import func, select
|
||||||
from sqlalchemy.orm import make_transient
|
from sqlalchemy.orm import make_transient
|
||||||
|
|
||||||
|
from freqtrade.configuration.config_setup import setup_utils_configuration
|
||||||
from freqtrade.persistence import Order, Trade, init_db
|
from freqtrade.persistence import Order, Trade, init_db
|
||||||
from freqtrade.persistence.migrations import set_sequence_ids
|
from freqtrade.persistence.migrations import set_sequence_ids
|
||||||
from freqtrade.persistence.pairlock import PairLock
|
from freqtrade.persistence.pairlock import PairLock
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, Optional, Tuple
|
from typing import Any
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from freqtrade.configuration import setup_utils_configuration
|
|
||||||
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 ConfigurationError, OperationalException
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||||
from freqtrade.util import render_template, render_template_with_fallback
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -20,12 +15,14 @@ logger = logging.getLogger(__name__)
|
|||||||
req_timeout = 30
|
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, ...)
|
||||||
:param args: Cli args from Arguments()
|
:param args: Cli args from Arguments()
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
from freqtrade.configuration.directory_operations import copy_sample_files, create_userdata_dir
|
||||||
|
|
||||||
if "user_data_dir" in args and args["user_data_dir"]:
|
if "user_data_dir" in args and args["user_data_dir"]:
|
||||||
userdir = create_userdata_dir(args["user_data_dir"], create_dir=True)
|
userdir = create_userdata_dir(args["user_data_dir"], create_dir=True)
|
||||||
copy_sample_files(userdir, overwrite=args["reset"])
|
copy_sample_files(userdir, overwrite=args["reset"])
|
||||||
@@ -38,6 +35,8 @@ def deploy_new_strategy(strategy_name: str, strategy_path: Path, subtemplate: st
|
|||||||
"""
|
"""
|
||||||
Deploy new strategy from template to strategy_path
|
Deploy new strategy from template to strategy_path
|
||||||
"""
|
"""
|
||||||
|
from freqtrade.util import render_template, render_template_with_fallback
|
||||||
|
|
||||||
fallback = "full"
|
fallback = "full"
|
||||||
attributes = render_template_with_fallback(
|
attributes = render_template_with_fallback(
|
||||||
templatefile=f"strategy_subtemplates/strategy_attributes_{subtemplate}.j2",
|
templatefile=f"strategy_subtemplates/strategy_attributes_{subtemplate}.j2",
|
||||||
@@ -81,11 +80,20 @@ def deploy_new_strategy(strategy_name: str, strategy_path: Path, subtemplate: st
|
|||||||
strategy_path.write_text(strategy_text)
|
strategy_path.write_text(strategy_text)
|
||||||
|
|
||||||
|
|
||||||
def start_new_strategy(args: Dict[str, Any]) -> None:
|
def start_new_strategy(args: dict[str, Any]) -> None:
|
||||||
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
|
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|
||||||
if "strategy" in args and args["strategy"]:
|
if "strategy" in args and args["strategy"]:
|
||||||
new_path = config["user_data_dir"] / USERPATH_STRATEGIES / (args["strategy"] + ".py")
|
if "strategy_path" in args and args["strategy_path"]:
|
||||||
|
strategy_dir = Path(args["strategy_path"])
|
||||||
|
else:
|
||||||
|
strategy_dir = config["user_data_dir"] / USERPATH_STRATEGIES
|
||||||
|
if not strategy_dir.is_dir():
|
||||||
|
logger.info(f"Creating strategy directory {strategy_dir}")
|
||||||
|
strategy_dir.mkdir(parents=True)
|
||||||
|
new_path = strategy_dir / (args["strategy"] + ".py")
|
||||||
|
|
||||||
if new_path.exists():
|
if new_path.exists():
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
@@ -98,80 +106,14 @@ def start_new_strategy(args: Dict[str, Any]) -> None:
|
|||||||
raise ConfigurationError("`new-strategy` requires --strategy to be set.")
|
raise ConfigurationError("`new-strategy` requires --strategy to be set.")
|
||||||
|
|
||||||
|
|
||||||
def clean_ui_subdir(directory: Path):
|
def start_install_ui(args: dict[str, Any]) -> None:
|
||||||
if directory.is_dir():
|
from freqtrade.commands.deploy_ui import (
|
||||||
logger.info("Removing UI directory content.")
|
clean_ui_subdir,
|
||||||
|
download_and_install_ui,
|
||||||
|
get_ui_download_url,
|
||||||
|
read_ui_version,
|
||||||
|
)
|
||||||
|
|
||||||
for p in reversed(list(directory.glob("**/*"))): # iterate contents from leaves to root
|
|
||||||
if p.name in (".gitkeep", "fallback_file.html"):
|
|
||||||
continue
|
|
||||||
if p.is_file():
|
|
||||||
p.unlink()
|
|
||||||
elif p.is_dir():
|
|
||||||
p.rmdir()
|
|
||||||
|
|
||||||
|
|
||||||
def read_ui_version(dest_folder: Path) -> Optional[str]:
|
|
||||||
file = dest_folder / ".uiversion"
|
|
||||||
if not file.is_file():
|
|
||||||
return None
|
|
||||||
|
|
||||||
with file.open("r") as f:
|
|
||||||
return f.read()
|
|
||||||
|
|
||||||
|
|
||||||
def download_and_install_ui(dest_folder: Path, dl_url: str, version: str):
|
|
||||||
from io import BytesIO
|
|
||||||
from zipfile import ZipFile
|
|
||||||
|
|
||||||
logger.info(f"Downloading {dl_url}")
|
|
||||||
resp = requests.get(dl_url, timeout=req_timeout).content
|
|
||||||
dest_folder.mkdir(parents=True, exist_ok=True)
|
|
||||||
with ZipFile(BytesIO(resp)) as zf:
|
|
||||||
for fn in zf.filelist:
|
|
||||||
with zf.open(fn) as x:
|
|
||||||
destfile = dest_folder / fn.filename
|
|
||||||
if fn.is_dir():
|
|
||||||
destfile.mkdir(exist_ok=True)
|
|
||||||
else:
|
|
||||||
destfile.write_bytes(x.read())
|
|
||||||
with (dest_folder / ".uiversion").open("w") as f:
|
|
||||||
f.write(version)
|
|
||||||
|
|
||||||
|
|
||||||
def get_ui_download_url(version: Optional[str] = None) -> Tuple[str, str]:
|
|
||||||
base_url = "https://api.github.com/repos/freqtrade/frequi/"
|
|
||||||
# Get base UI Repo path
|
|
||||||
|
|
||||||
resp = requests.get(f"{base_url}releases", timeout=req_timeout)
|
|
||||||
resp.raise_for_status()
|
|
||||||
r = resp.json()
|
|
||||||
|
|
||||||
if version:
|
|
||||||
tmp = [x for x in r if x["name"] == version]
|
|
||||||
if tmp:
|
|
||||||
latest_version = tmp[0]["name"]
|
|
||||||
assets = tmp[0].get("assets", [])
|
|
||||||
else:
|
|
||||||
raise ValueError("UI-Version not found.")
|
|
||||||
else:
|
|
||||||
latest_version = r[0]["name"]
|
|
||||||
assets = r[0].get("assets", [])
|
|
||||||
dl_url = ""
|
|
||||||
if assets and len(assets) > 0:
|
|
||||||
dl_url = assets[0]["browser_download_url"]
|
|
||||||
|
|
||||||
# URL not found - try assets url
|
|
||||||
if not dl_url:
|
|
||||||
assets = r[0]["assets_url"]
|
|
||||||
resp = requests.get(assets, timeout=req_timeout)
|
|
||||||
r = resp.json()
|
|
||||||
dl_url = r[0]["browser_download_url"]
|
|
||||||
|
|
||||||
return dl_url, latest_version
|
|
||||||
|
|
||||||
|
|
||||||
def start_install_ui(args: Dict[str, Any]) -> None:
|
|
||||||
dest_folder = Path(__file__).parents[1] / "rpc/api_server/ui/installed/"
|
dest_folder = Path(__file__).parents[1] / "rpc/api_server/ui/installed/"
|
||||||
# First make sure the assets are removed.
|
# First make sure the assets are removed.
|
||||||
dl_url, latest_version = get_ui_download_url(args.get("ui_version"))
|
dl_url, latest_version = get_ui_download_url(args.get("ui_version"))
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Timeout for requests
|
||||||
|
req_timeout = 30
|
||||||
|
|
||||||
|
|
||||||
|
def clean_ui_subdir(directory: Path):
|
||||||
|
if directory.is_dir():
|
||||||
|
logger.info("Removing UI directory content.")
|
||||||
|
|
||||||
|
for p in reversed(list(directory.glob("**/*"))): # iterate contents from leaves to root
|
||||||
|
if p.name in (".gitkeep", "fallback_file.html"):
|
||||||
|
continue
|
||||||
|
if p.is_file():
|
||||||
|
p.unlink()
|
||||||
|
elif p.is_dir():
|
||||||
|
p.rmdir()
|
||||||
|
|
||||||
|
|
||||||
|
def read_ui_version(dest_folder: Path) -> str | None:
|
||||||
|
file = dest_folder / ".uiversion"
|
||||||
|
if not file.is_file():
|
||||||
|
return None
|
||||||
|
|
||||||
|
with file.open("r") as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
def download_and_install_ui(dest_folder: Path, dl_url: str, version: str):
|
||||||
|
from io import BytesIO
|
||||||
|
from zipfile import ZipFile
|
||||||
|
|
||||||
|
logger.info(f"Downloading {dl_url}")
|
||||||
|
resp = requests.get(dl_url, timeout=req_timeout).content
|
||||||
|
dest_folder.mkdir(parents=True, exist_ok=True)
|
||||||
|
with ZipFile(BytesIO(resp)) as zf:
|
||||||
|
for fn in zf.filelist:
|
||||||
|
with zf.open(fn) as x:
|
||||||
|
destfile = dest_folder / fn.filename
|
||||||
|
if fn.is_dir():
|
||||||
|
destfile.mkdir(exist_ok=True)
|
||||||
|
else:
|
||||||
|
destfile.write_bytes(x.read())
|
||||||
|
with (dest_folder / ".uiversion").open("w") as f:
|
||||||
|
f.write(version)
|
||||||
|
|
||||||
|
|
||||||
|
def get_ui_download_url(version: str | None = None) -> tuple[str, str]:
|
||||||
|
base_url = "https://api.github.com/repos/freqtrade/frequi/"
|
||||||
|
# Get base UI Repo path
|
||||||
|
|
||||||
|
resp = requests.get(f"{base_url}releases", timeout=req_timeout)
|
||||||
|
resp.raise_for_status()
|
||||||
|
r = resp.json()
|
||||||
|
|
||||||
|
if version:
|
||||||
|
tmp = [x for x in r if x["name"] == version]
|
||||||
|
if tmp:
|
||||||
|
latest_version = tmp[0]["name"]
|
||||||
|
assets = tmp[0].get("assets", [])
|
||||||
|
else:
|
||||||
|
raise ValueError("UI-Version not found.")
|
||||||
|
else:
|
||||||
|
latest_version = r[0]["name"]
|
||||||
|
assets = r[0].get("assets", [])
|
||||||
|
dl_url = ""
|
||||||
|
if assets and len(assets) > 0:
|
||||||
|
dl_url = assets[0]["browser_download_url"]
|
||||||
|
|
||||||
|
# URL not found - try assets url
|
||||||
|
if not dl_url:
|
||||||
|
assets = r[0]["assets_url"]
|
||||||
|
resp = requests.get(assets, timeout=req_timeout)
|
||||||
|
r = resp.json()
|
||||||
|
dl_url = r[0]["browser_download_url"]
|
||||||
|
|
||||||
|
return dl_url, latest_version
|
||||||
@@ -1,22 +1,21 @@
|
|||||||
import logging
|
import logging
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from freqtrade.configuration import setup_utils_configuration
|
|
||||||
from freqtrade.data.btanalysis import get_latest_hyperopt_file
|
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.optimize.optimize_reports import show_backtest_result
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def start_hyperopt_list(args: Dict[str, Any]) -> None:
|
def start_hyperopt_list(args: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
List hyperopt epochs previously evaluated
|
List hyperopt epochs previously evaluated
|
||||||
"""
|
"""
|
||||||
from freqtrade.optimize.hyperopt_output import HyperoptOutput
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
|
from freqtrade.data.btanalysis import get_latest_hyperopt_file
|
||||||
|
from freqtrade.optimize.hyperopt.hyperopt_output import HyperoptOutput
|
||||||
from freqtrade.optimize.hyperopt_tools import HyperoptTools
|
from freqtrade.optimize.hyperopt_tools import HyperoptTools
|
||||||
|
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
@@ -57,11 +56,14 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
|
|||||||
HyperoptTools.export_csv_file(config, epochs, export_csv)
|
HyperoptTools.export_csv_file(config, epochs, export_csv)
|
||||||
|
|
||||||
|
|
||||||
def start_hyperopt_show(args: Dict[str, Any]) -> None:
|
def start_hyperopt_show(args: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Show details of a hyperopt epoch previously evaluated
|
Show details of a hyperopt epoch previously evaluated
|
||||||
"""
|
"""
|
||||||
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
|
from freqtrade.data.btanalysis import get_latest_hyperopt_file
|
||||||
from freqtrade.optimize.hyperopt_tools import HyperoptTools
|
from freqtrade.optimize.hyperopt_tools import HyperoptTools
|
||||||
|
from freqtrade.optimize.optimize_reports import show_backtest_result
|
||||||
|
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +1,34 @@
|
|||||||
import csv
|
import csv
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Dict, List, Union
|
from typing import Any
|
||||||
|
|
||||||
import rapidjson
|
|
||||||
from rich.console import Console
|
|
||||||
from rich.table import Table
|
|
||||||
from rich.text import Text
|
|
||||||
|
|
||||||
from freqtrade.configuration import setup_utils_configuration
|
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import ConfigurationError, OperationalException
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||||
from freqtrade.exchange import list_available_exchanges, market_is_active
|
from freqtrade.ft_types import ValidExchangesType
|
||||||
from freqtrade.misc import parse_db_uri_for_logging, plural
|
|
||||||
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
|
||||||
from freqtrade.types.valid_exchanges_type import ValidExchangesType
|
|
||||||
from freqtrade.util import print_rich_table
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def start_list_exchanges(args: Dict[str, Any]) -> None:
|
def start_list_exchanges(args: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Print available exchanges
|
Print available exchanges
|
||||||
:param args: Cli args from Arguments()
|
:param args: Cli args from Arguments()
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
available_exchanges: List[ValidExchangesType] = list_available_exchanges(
|
from rich.console import Console
|
||||||
|
from rich.table import Table
|
||||||
|
from rich.text import Text
|
||||||
|
|
||||||
|
from freqtrade.exchange import list_available_exchanges
|
||||||
|
|
||||||
|
available_exchanges: list[ValidExchangesType] = list_available_exchanges(
|
||||||
args["list_exchanges_all"]
|
args["list_exchanges_all"]
|
||||||
)
|
)
|
||||||
|
|
||||||
if args["print_one_column"]:
|
if args["print_one_column"]:
|
||||||
print("\n".join([e["name"] for e in available_exchanges]))
|
print("\n".join([e["classname"] for e in available_exchanges]))
|
||||||
else:
|
else:
|
||||||
if args["list_exchanges_all"]:
|
if args["list_exchanges_all"]:
|
||||||
title = (
|
title = (
|
||||||
@@ -46,14 +42,20 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
|
|||||||
table = Table(title=title)
|
table = Table(title=title)
|
||||||
|
|
||||||
table.add_column("Exchange Name")
|
table.add_column("Exchange Name")
|
||||||
|
table.add_column("Class Name")
|
||||||
table.add_column("Markets")
|
table.add_column("Markets")
|
||||||
table.add_column("Reason")
|
table.add_column("Reason")
|
||||||
|
|
||||||
for exchange in available_exchanges:
|
for exchange in available_exchanges:
|
||||||
name = Text(exchange["name"])
|
name = Text(exchange["name"])
|
||||||
if exchange["supported"]:
|
if exchange["supported"]:
|
||||||
name.append(" (Official)", style="italic")
|
name.append(" (Supported)", style="italic")
|
||||||
name.stylize("green bold")
|
name.stylize("green bold")
|
||||||
|
classname = Text(exchange["classname"])
|
||||||
|
if exchange["is_alias"]:
|
||||||
|
name.stylize("strike")
|
||||||
|
classname.stylize("strike")
|
||||||
|
classname.append(f" (use {exchange['alias_for']})", style="italic")
|
||||||
|
|
||||||
trade_modes = Text(
|
trade_modes = Text(
|
||||||
", ".join(
|
", ".join(
|
||||||
@@ -68,6 +70,7 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
|
|||||||
|
|
||||||
table.add_row(
|
table.add_row(
|
||||||
name,
|
name,
|
||||||
|
classname,
|
||||||
trade_modes,
|
trade_modes,
|
||||||
exchange["comment"],
|
exchange["comment"],
|
||||||
style=None if exchange["valid"] else "red",
|
style=None if exchange["valid"] else "red",
|
||||||
@@ -78,9 +81,13 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
|
|||||||
console.print(table)
|
console.print(table)
|
||||||
|
|
||||||
|
|
||||||
def _print_objs_tabular(objs: List, print_colorized: bool) -> None:
|
def _print_objs_tabular(objs: list, print_colorized: bool) -> None:
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.table import Table
|
||||||
|
from rich.text import Text
|
||||||
|
|
||||||
names = [s["name"] for s in objs]
|
names = [s["name"] for s in objs]
|
||||||
objs_to_print: List[Dict[str, Union[Text, str]]] = [
|
objs_to_print: list[dict[str, Text | str]] = [
|
||||||
{
|
{
|
||||||
"name": Text(s["name"] if s["name"] else "--"),
|
"name": Text(s["name"] if s["name"] else "--"),
|
||||||
"location": s["location_rel"],
|
"location": s["location_rel"],
|
||||||
@@ -118,10 +125,13 @@ def _print_objs_tabular(objs: List, print_colorized: bool) -> None:
|
|||||||
console.print(table)
|
console.print(table)
|
||||||
|
|
||||||
|
|
||||||
def start_list_strategies(args: Dict[str, Any]) -> None:
|
def start_list_strategies(args: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Print files with Strategy custom classes available in the directory
|
Print files with Strategy custom classes available in the directory
|
||||||
"""
|
"""
|
||||||
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
|
from freqtrade.resolvers import StrategyResolver
|
||||||
|
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|
||||||
strategy_objs = StrategyResolver.search_all_objects(
|
strategy_objs = StrategyResolver.search_all_objects(
|
||||||
@@ -141,13 +151,15 @@ def start_list_strategies(args: Dict[str, Any]) -> None:
|
|||||||
_print_objs_tabular(strategy_objs, config.get("print_colorized", False))
|
_print_objs_tabular(strategy_objs, config.get("print_colorized", False))
|
||||||
|
|
||||||
|
|
||||||
def start_list_freqAI_models(args: Dict[str, Any]) -> None:
|
def start_list_freqAI_models(args: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Print files with FreqAI models custom classes available in the directory
|
Print files with FreqAI models custom classes available in the directory
|
||||||
"""
|
"""
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
from freqtrade.resolvers.freqaimodel_resolver import FreqaiModelResolver
|
from freqtrade.resolvers.freqaimodel_resolver import FreqaiModelResolver
|
||||||
|
|
||||||
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|
||||||
model_objs = FreqaiModelResolver.search_all_objects(config, not args["print_one_column"])
|
model_objs = FreqaiModelResolver.search_all_objects(config, not args["print_one_column"])
|
||||||
# Sort alphabetically
|
# Sort alphabetically
|
||||||
model_objs = sorted(model_objs, key=lambda x: x["name"])
|
model_objs = sorted(model_objs, key=lambda x: x["name"])
|
||||||
@@ -157,10 +169,31 @@ def start_list_freqAI_models(args: Dict[str, Any]) -> None:
|
|||||||
_print_objs_tabular(model_objs, config.get("print_colorized", False))
|
_print_objs_tabular(model_objs, config.get("print_colorized", False))
|
||||||
|
|
||||||
|
|
||||||
def start_list_timeframes(args: Dict[str, Any]) -> None:
|
def start_list_hyperopt_loss_functions(args: dict[str, Any]) -> None:
|
||||||
|
"""
|
||||||
|
Print files with FreqAI models custom classes available in the directory
|
||||||
|
"""
|
||||||
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
|
from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver
|
||||||
|
|
||||||
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|
||||||
|
model_objs = HyperOptLossResolver.search_all_objects(config, not args["print_one_column"])
|
||||||
|
# Sort alphabetically
|
||||||
|
model_objs = sorted(model_objs, key=lambda x: x["name"])
|
||||||
|
if args["print_one_column"]:
|
||||||
|
print("\n".join([s["name"] for s in model_objs]))
|
||||||
|
else:
|
||||||
|
_print_objs_tabular(model_objs, config.get("print_colorized", False))
|
||||||
|
|
||||||
|
|
||||||
|
def start_list_timeframes(args: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Print timeframes available on Exchange
|
Print timeframes available on Exchange
|
||||||
"""
|
"""
|
||||||
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
|
from freqtrade.resolvers import ExchangeResolver
|
||||||
|
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
|
||||||
# Do not use timeframe set in the config
|
# Do not use timeframe set in the config
|
||||||
config["timeframe"] = None
|
config["timeframe"] = None
|
||||||
@@ -177,13 +210,19 @@ def start_list_timeframes(args: Dict[str, Any]) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
|
def start_list_markets(args: dict[str, Any], pairs_only: bool = False) -> None:
|
||||||
"""
|
"""
|
||||||
Print pairs/markets on the exchange
|
Print pairs/markets on the exchange
|
||||||
:param args: Cli args from Arguments()
|
:param args: Cli args from Arguments()
|
||||||
:param pairs_only: if True print only pairs, otherwise print all instruments (markets)
|
:param pairs_only: if True print only pairs, otherwise print all instruments (markets)
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
|
from freqtrade.exchange import market_is_active
|
||||||
|
from freqtrade.misc import plural
|
||||||
|
from freqtrade.resolvers import ExchangeResolver
|
||||||
|
from freqtrade.util import print_rich_table
|
||||||
|
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
|
||||||
|
|
||||||
# Init exchange
|
# Init exchange
|
||||||
@@ -274,6 +313,8 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
|
|||||||
elif args.get("print_one_column", False):
|
elif args.get("print_one_column", False):
|
||||||
print("\n".join(pairs.keys()))
|
print("\n".join(pairs.keys()))
|
||||||
elif args.get("list_pairs_print_json", False):
|
elif args.get("list_pairs_print_json", False):
|
||||||
|
import rapidjson
|
||||||
|
|
||||||
print(rapidjson.dumps(list(pairs.keys()), default=str))
|
print(rapidjson.dumps(list(pairs.keys()), default=str))
|
||||||
elif args.get("print_csv", False):
|
elif args.get("print_csv", False):
|
||||||
writer = csv.DictWriter(sys.stdout, fieldnames=headers)
|
writer = csv.DictWriter(sys.stdout, fieldnames=headers)
|
||||||
@@ -289,12 +330,14 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
|
|||||||
print(f"{summary_str}.")
|
print(f"{summary_str}.")
|
||||||
|
|
||||||
|
|
||||||
def start_show_trades(args: Dict[str, Any]) -> None:
|
def start_show_trades(args: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Show trades
|
Show trades
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
|
from freqtrade.misc import parse_db_uri_for_logging
|
||||||
from freqtrade.persistence import Trade, init_db
|
from freqtrade.persistence import Trade, init_db
|
||||||
|
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from freqtrade import constants
|
from freqtrade import constants
|
||||||
from freqtrade.configuration import setup_utils_configuration
|
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import ConfigurationError, OperationalException
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||||
from freqtrade.util import fmt_coin
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[str, Any]:
|
def setup_optimize_configuration(args: dict[str, Any], method: RunMode) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Prepare the configuration for the Hyperopt module
|
Prepare the configuration for the Hyperopt module
|
||||||
:param args: Cli args from Arguments()
|
:param args: Cli args from Arguments()
|
||||||
:param method: Bot running mode
|
:param method: Bot running mode
|
||||||
:return: Configuration
|
:return: Configuration
|
||||||
"""
|
"""
|
||||||
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
|
from freqtrade.util import fmt_coin, get_dry_run_wallet
|
||||||
|
|
||||||
config = setup_utils_configuration(args, method)
|
config = setup_utils_configuration(args, method)
|
||||||
|
|
||||||
no_unlimited_runmodes = {
|
no_unlimited_runmodes = {
|
||||||
@@ -25,7 +26,7 @@ def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[
|
|||||||
RunMode.HYPEROPT: "hyperoptimization",
|
RunMode.HYPEROPT: "hyperoptimization",
|
||||||
}
|
}
|
||||||
if method in no_unlimited_runmodes.keys():
|
if method in no_unlimited_runmodes.keys():
|
||||||
wallet_size = config["dry_run_wallet"] * config["tradable_balance_ratio"]
|
wallet_size = get_dry_run_wallet(config) * config["tradable_balance_ratio"]
|
||||||
# tradable_balance_ratio
|
# tradable_balance_ratio
|
||||||
if (
|
if (
|
||||||
config["stake_amount"] != constants.UNLIMITED_STAKE_AMOUNT
|
config["stake_amount"] != constants.UNLIMITED_STAKE_AMOUNT
|
||||||
@@ -41,7 +42,7 @@ def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[
|
|||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
def start_backtesting(args: Dict[str, Any]) -> None:
|
def start_backtesting(args: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Start Backtesting script
|
Start Backtesting script
|
||||||
:param args: Cli args from Arguments()
|
:param args: Cli args from Arguments()
|
||||||
@@ -60,10 +61,11 @@ def start_backtesting(args: Dict[str, Any]) -> None:
|
|||||||
backtesting.start()
|
backtesting.start()
|
||||||
|
|
||||||
|
|
||||||
def start_backtesting_show(args: Dict[str, Any]) -> None:
|
def start_backtesting_show(args: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Show previous backtest result
|
Show previous backtest result
|
||||||
"""
|
"""
|
||||||
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
|
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|
||||||
@@ -76,7 +78,7 @@ def start_backtesting_show(args: Dict[str, Any]) -> None:
|
|||||||
show_sorted_pairlist(config, results)
|
show_sorted_pairlist(config, results)
|
||||||
|
|
||||||
|
|
||||||
def start_hyperopt(args: Dict[str, Any]) -> None:
|
def start_hyperopt(args: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Start hyperopt script
|
Start hyperopt script
|
||||||
:param args: Cli args from Arguments()
|
:param args: Cli args from Arguments()
|
||||||
@@ -121,7 +123,7 @@ def start_hyperopt(args: Dict[str, Any]) -> None:
|
|||||||
# Same in Edge and Backtesting start() functions.
|
# Same in Edge and Backtesting start() functions.
|
||||||
|
|
||||||
|
|
||||||
def start_edge(args: Dict[str, Any]) -> None:
|
def start_edge(args: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Start Edge script
|
Start Edge script
|
||||||
:param args: Cli args from Arguments()
|
:param args: Cli args from Arguments()
|
||||||
@@ -138,24 +140,26 @@ def start_edge(args: Dict[str, Any]) -> None:
|
|||||||
edge_cli.start()
|
edge_cli.start()
|
||||||
|
|
||||||
|
|
||||||
def start_lookahead_analysis(args: Dict[str, Any]) -> None:
|
def start_lookahead_analysis(args: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Start the backtest bias tester script
|
Start the backtest bias tester script
|
||||||
:param args: Cli args from Arguments()
|
:param args: Cli args from Arguments()
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
from freqtrade.optimize.analysis.lookahead_helpers import LookaheadAnalysisSubFunctions
|
from freqtrade.optimize.analysis.lookahead_helpers import LookaheadAnalysisSubFunctions
|
||||||
|
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
LookaheadAnalysisSubFunctions.start(config)
|
LookaheadAnalysisSubFunctions.start(config)
|
||||||
|
|
||||||
|
|
||||||
def start_recursive_analysis(args: Dict[str, Any]) -> None:
|
def start_recursive_analysis(args: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Start the backtest recursive tester script
|
Start the backtest recursive tester script
|
||||||
:param args: Cli args from Arguments()
|
:param args: Cli args from Arguments()
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
from freqtrade.optimize.analysis.recursive_helpers import RecursiveAnalysisSubFunctions
|
from freqtrade.optimize.analysis.recursive_helpers import RecursiveAnalysisSubFunctions
|
||||||
|
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
import rapidjson
|
import rapidjson
|
||||||
|
|
||||||
from freqtrade.configuration import setup_utils_configuration
|
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.resolvers import ExchangeResolver
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def start_test_pairlist(args: Dict[str, Any]) -> None:
|
def start_test_pairlist(args: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Test Pairlist configuration
|
Test Pairlist configuration
|
||||||
"""
|
"""
|
||||||
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
from freqtrade.persistence import FtNoDBContext
|
from freqtrade.persistence import FtNoDBContext
|
||||||
from freqtrade.plugins.pairlistmanager import PairListManager
|
from freqtrade.plugins.pairlistmanager import PairListManager
|
||||||
|
from freqtrade.resolvers import ExchangeResolver
|
||||||
|
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from freqtrade.configuration import setup_utils_configuration
|
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import ConfigurationError
|
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 ConfigurationError(
|
raise ConfigurationError(
|
||||||
"You need to specify either `--datadir` or `--config` "
|
"You need to specify either `--datadir` or `--config` "
|
||||||
@@ -13,11 +12,12 @@ def validate_plot_args(args: Dict[str, Any]) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def start_plot_dataframe(args: Dict[str, Any]) -> None:
|
def start_plot_dataframe(args: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Entrypoint for dataframe plotting
|
Entrypoint for dataframe plotting
|
||||||
"""
|
"""
|
||||||
# Import here to avoid errors if plot-dependencies are not installed.
|
# Import here to avoid errors if plot-dependencies are not installed.
|
||||||
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
from freqtrade.plot.plotting import load_and_plot_trades
|
from freqtrade.plot.plotting import load_and_plot_trades
|
||||||
|
|
||||||
validate_plot_args(args)
|
validate_plot_args(args)
|
||||||
@@ -26,11 +26,12 @@ def start_plot_dataframe(args: Dict[str, Any]) -> None:
|
|||||||
load_and_plot_trades(config)
|
load_and_plot_trades(config)
|
||||||
|
|
||||||
|
|
||||||
def start_plot_profit(args: Dict[str, Any]) -> None:
|
def start_plot_profit(args: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Entrypoint for plot_profit
|
Entrypoint for plot_profit
|
||||||
"""
|
"""
|
||||||
# Import here to avoid errors if plot-dependencies are not installed.
|
# Import here to avoid errors if plot-dependencies are not installed.
|
||||||
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
from freqtrade.plot.plotting import plot_profit
|
from freqtrade.plot.plotting import plot_profit
|
||||||
|
|
||||||
validate_plot_args(args)
|
validate_plot_args(args)
|
||||||
|
|||||||
@@ -1,27 +1,22 @@
|
|||||||
import logging
|
import logging
|
||||||
import sys
|
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from freqtrade.configuration import setup_utils_configuration
|
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.resolvers import StrategyResolver
|
|
||||||
from freqtrade.strategy.strategyupdater import StrategyUpdater
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def start_strategy_update(args: Dict[str, Any]) -> None:
|
def start_strategy_update(args: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Start the strategy updating script
|
Start the strategy updating script
|
||||||
:param args: Cli args from Arguments()
|
:param args: Cli args from Arguments()
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
if sys.version_info == (3, 8): # pragma: no cover
|
from freqtrade.resolvers import StrategyResolver
|
||||||
sys.exit("Freqtrade strategy updater requires Python version >= 3.9")
|
|
||||||
|
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|
||||||
@@ -49,6 +44,8 @@ def start_strategy_update(args: Dict[str, Any]) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def start_conversion(strategy_obj, config):
|
def start_conversion(strategy_obj, config):
|
||||||
|
from freqtrade.strategy.strategyupdater import StrategyUpdater
|
||||||
|
|
||||||
print(f"Conversion of {Path(strategy_obj['location']).name} started.")
|
print(f"Conversion of {Path(strategy_obj['location']).name} started.")
|
||||||
instance_strategy_updater = StrategyUpdater()
|
instance_strategy_updater = StrategyUpdater()
|
||||||
start = time.perf_counter()
|
start = time.perf_counter()
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import logging
|
import logging
|
||||||
import signal
|
import signal
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def start_trading(args: Dict[str, Any]) -> int:
|
def start_trading(args: dict[str, Any]) -> int:
|
||||||
"""
|
"""
|
||||||
Main entry point for trading mode
|
Main entry point for trading mode
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
|
|
||||||
|
|
||||||
def start_webserver(args: Dict[str, Any]) -> None:
|
def start_webserver(args: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Main entry point for webserver mode
|
Main entry point for webserver mode
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
# Required json-schema for user specified config
|
# Required json-schema for user specified config
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
from freqtrade.constants import (
|
from freqtrade.constants import (
|
||||||
AVAILABLE_DATAHANDLERS,
|
AVAILABLE_DATAHANDLERS,
|
||||||
AVAILABLE_PAIRLISTS,
|
AVAILABLE_PAIRLISTS,
|
||||||
AVAILABLE_PROTECTIONS,
|
|
||||||
BACKTEST_BREAKDOWNS,
|
BACKTEST_BREAKDOWNS,
|
||||||
DRY_RUN_WALLET,
|
DRY_RUN_WALLET,
|
||||||
EXPORT_OPTIONS,
|
EXPORT_OPTIONS,
|
||||||
@@ -24,7 +22,7 @@ from freqtrade.constants import (
|
|||||||
from freqtrade.enums import RPCMessageType
|
from freqtrade.enums import RPCMessageType
|
||||||
|
|
||||||
|
|
||||||
__MESSAGE_TYPE_DICT: Dict[str, Dict[str, str]] = {x: {"type": "object"} for x in RPCMessageType}
|
__MESSAGE_TYPE_DICT: dict[str, dict[str, str]] = {x: {"type": "object"} for x in RPCMessageType}
|
||||||
|
|
||||||
__IN_STRATEGY = "\nUsually specified in the strategy and missing in the configuration."
|
__IN_STRATEGY = "\nUsually specified in the strategy and missing in the configuration."
|
||||||
|
|
||||||
@@ -36,11 +34,6 @@ CONF_SCHEMA = {
|
|||||||
"type": ["integer", "number"],
|
"type": ["integer", "number"],
|
||||||
"minimum": -1,
|
"minimum": -1,
|
||||||
},
|
},
|
||||||
"new_pairs_days": {
|
|
||||||
"description": "Download data of new pairs for given number of days",
|
|
||||||
"type": "integer",
|
|
||||||
"default": 30,
|
|
||||||
},
|
|
||||||
"timeframe": {
|
"timeframe": {
|
||||||
"description": (
|
"description": (
|
||||||
f"The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). {__IN_STRATEGY}"
|
f"The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). {__IN_STRATEGY}"
|
||||||
@@ -92,8 +85,10 @@ CONF_SCHEMA = {
|
|||||||
},
|
},
|
||||||
"dry_run_wallet": {
|
"dry_run_wallet": {
|
||||||
"description": "Initial wallet balance for dry run mode.",
|
"description": "Initial wallet balance for dry run mode.",
|
||||||
"type": "number",
|
"type": ["number", "object"],
|
||||||
"default": DRY_RUN_WALLET,
|
"default": DRY_RUN_WALLET,
|
||||||
|
"patternProperties": {r"^[a-zA-Z0-9]+$": {"type": "number"}},
|
||||||
|
"additionalProperties": False,
|
||||||
},
|
},
|
||||||
"cancel_open_orders_on_exit": {
|
"cancel_open_orders_on_exit": {
|
||||||
"description": "Cancel open orders when exiting.",
|
"description": "Cancel open orders when exiting.",
|
||||||
@@ -185,6 +180,7 @@ CONF_SCHEMA = {
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": False,
|
"default": False,
|
||||||
},
|
},
|
||||||
|
# Lookahead analysis section
|
||||||
"minimum_trade_amount": {
|
"minimum_trade_amount": {
|
||||||
"description": "Minimum amount for a trade - only used for lookahead-analysis",
|
"description": "Minimum amount for a trade - only used for lookahead-analysis",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
@@ -453,54 +449,7 @@ CONF_SCHEMA = {
|
|||||||
"required": ["method"],
|
"required": ["method"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"protections": {
|
# RPC section
|
||||||
"description": "Configuration for various protections.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"method": {
|
|
||||||
"description": "Method used for the protection.",
|
|
||||||
"type": "string",
|
|
||||||
"enum": AVAILABLE_PROTECTIONS,
|
|
||||||
},
|
|
||||||
"stop_duration": {
|
|
||||||
"description": (
|
|
||||||
"Duration to lock the pair after a protection is triggered, "
|
|
||||||
"in minutes."
|
|
||||||
),
|
|
||||||
"type": "number",
|
|
||||||
"minimum": 0.0,
|
|
||||||
},
|
|
||||||
"stop_duration_candles": {
|
|
||||||
"description": (
|
|
||||||
"Duration to lock the pair after a protection is triggered, in "
|
|
||||||
"number of candles."
|
|
||||||
),
|
|
||||||
"type": "number",
|
|
||||||
"minimum": 0,
|
|
||||||
},
|
|
||||||
"trade_limit": {
|
|
||||||
"description": "Minimum number of trades required during lookback period.",
|
|
||||||
"type": "number",
|
|
||||||
"minimum": 1,
|
|
||||||
},
|
|
||||||
"lookback_period": {
|
|
||||||
"description": "Period to look back for protection checks, in minutes.",
|
|
||||||
"type": "number",
|
|
||||||
"minimum": 1,
|
|
||||||
},
|
|
||||||
"lookback_period_candles": {
|
|
||||||
"description": (
|
|
||||||
"Period to look back for protection checks, in number " "of candles."
|
|
||||||
),
|
|
||||||
"type": "number",
|
|
||||||
"minimum": 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"required": ["method"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"telegram": {
|
"telegram": {
|
||||||
"description": "Telegram settings.",
|
"description": "Telegram settings.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -511,7 +460,11 @@ CONF_SCHEMA = {
|
|||||||
},
|
},
|
||||||
"token": {"description": "Telegram bot token.", "type": "string"},
|
"token": {"description": "Telegram bot token.", "type": "string"},
|
||||||
"chat_id": {
|
"chat_id": {
|
||||||
"description": "Telegram chat ID",
|
"description": "Telegram chat or group ID",
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"topic_id": {
|
||||||
|
"description": "Telegram topic ID - only applicable for group chats",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
"allow_custom_messages": {
|
"allow_custom_messages": {
|
||||||
@@ -570,8 +523,11 @@ CONF_SCHEMA = {
|
|||||||
},
|
},
|
||||||
"exit_fill": {
|
"exit_fill": {
|
||||||
"description": "Telegram setting for exit fill signals.",
|
"description": "Telegram setting for exit fill signals.",
|
||||||
"type": "string",
|
"type": ["string", "object"],
|
||||||
"enum": TELEGRAM_SETTING_OPTIONS,
|
"additionalProperties": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": TELEGRAM_SETTING_OPTIONS,
|
||||||
|
},
|
||||||
"default": "on",
|
"default": "on",
|
||||||
},
|
},
|
||||||
"exit_cancel": {
|
"exit_cancel": {
|
||||||
@@ -701,6 +657,7 @@ CONF_SCHEMA = {
|
|||||||
},
|
},
|
||||||
"required": ["enabled", "listen_ip_address", "listen_port", "username", "password"],
|
"required": ["enabled", "listen_ip_address", "listen_port", "username", "password"],
|
||||||
},
|
},
|
||||||
|
# end of RPC section
|
||||||
"db_url": {
|
"db_url": {
|
||||||
"description": "Database connection URL.",
|
"description": "Database connection URL.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -734,7 +691,7 @@ CONF_SCHEMA = {
|
|||||||
"default": {},
|
"default": {},
|
||||||
"properties": {
|
"properties": {
|
||||||
"process_throttle_secs": {
|
"process_throttle_secs": {
|
||||||
"description": "Throttle time in seconds for processing.",
|
"description": "Minimum loop duration for one bot iteration in seconds.",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
},
|
},
|
||||||
"interval": {
|
"interval": {
|
||||||
@@ -763,11 +720,26 @@ CONF_SCHEMA = {
|
|||||||
"description": f"Enable position adjustment. {__IN_STRATEGY}",
|
"description": f"Enable position adjustment. {__IN_STRATEGY}",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
},
|
},
|
||||||
|
# Download data section
|
||||||
|
"new_pairs_days": {
|
||||||
|
"description": "Download data of new pairs for given number of days",
|
||||||
|
"type": "integer",
|
||||||
|
"default": 30,
|
||||||
|
},
|
||||||
|
"download_trades": {
|
||||||
|
"description": "Download trades data by default (instead of ohlcv data).",
|
||||||
|
"type": "boolean",
|
||||||
|
},
|
||||||
"max_entry_position_adjustment": {
|
"max_entry_position_adjustment": {
|
||||||
"description": f"Maximum entry position adjustment allowed. {__IN_STRATEGY}",
|
"description": f"Maximum entry position adjustment allowed. {__IN_STRATEGY}",
|
||||||
"type": ["integer", "number"],
|
"type": ["integer", "number"],
|
||||||
"minimum": -1,
|
"minimum": -1,
|
||||||
},
|
},
|
||||||
|
"add_config_files": {
|
||||||
|
"description": "Additional configuration files to load.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {"type": "string"},
|
||||||
|
},
|
||||||
"orderflow": {
|
"orderflow": {
|
||||||
"description": "Settings related to order flow.",
|
"description": "Settings related to order flow.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -853,6 +825,14 @@ CONF_SCHEMA = {
|
|||||||
"items": {"type": "string"},
|
"items": {"type": "string"},
|
||||||
"uniqueItems": True,
|
"uniqueItems": True,
|
||||||
},
|
},
|
||||||
|
"log_responses": {
|
||||||
|
"description": (
|
||||||
|
"Log responses from the exchange."
|
||||||
|
"Useful/required to debug issues with order processing."
|
||||||
|
),
|
||||||
|
"type": "boolean",
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
"unknown_fee_rate": {
|
"unknown_fee_rate": {
|
||||||
"description": "Fee rate for unknown markets.",
|
"description": "Fee rate for unknown markets.",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
@@ -1024,6 +1004,13 @@ CONF_SCHEMA = {
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "example",
|
"default": "example",
|
||||||
},
|
},
|
||||||
|
"wait_for_training_iteration_on_reload": {
|
||||||
|
"description": (
|
||||||
|
"Wait for the next training iteration to complete after /reload or ctrl+c."
|
||||||
|
),
|
||||||
|
"type": "boolean",
|
||||||
|
"default": True,
|
||||||
|
},
|
||||||
"feature_parameters": {
|
"feature_parameters": {
|
||||||
"description": "The parameters used to engineer the feature set",
|
"description": "The parameters used to engineer the feature set",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
@@ -14,12 +14,16 @@ def sanitize_config(config: Config, *, show_sensitive: bool = False) -> Config:
|
|||||||
return config
|
return config
|
||||||
keys_to_remove = [
|
keys_to_remove = [
|
||||||
"exchange.key",
|
"exchange.key",
|
||||||
|
"exchange.api_key",
|
||||||
"exchange.apiKey",
|
"exchange.apiKey",
|
||||||
"exchange.secret",
|
"exchange.secret",
|
||||||
"exchange.password",
|
"exchange.password",
|
||||||
"exchange.uid",
|
"exchange.uid",
|
||||||
|
"exchange.account_id",
|
||||||
"exchange.accountId",
|
"exchange.accountId",
|
||||||
|
"exchange.wallet_address",
|
||||||
"exchange.walletAddress",
|
"exchange.walletAddress",
|
||||||
|
"exchange.private_key",
|
||||||
"exchange.privateKey",
|
"exchange.privateKey",
|
||||||
"telegram.token",
|
"telegram.token",
|
||||||
"telegram.chat_id",
|
"telegram.chat_id",
|
||||||
@@ -33,8 +37,10 @@ def sanitize_config(config: Config, *, show_sensitive: bool = False) -> Config:
|
|||||||
nested_config = config
|
nested_config = config
|
||||||
for nested_key in nested_keys[:-1]:
|
for nested_key in nested_keys[:-1]:
|
||||||
nested_config = nested_config.get(nested_key, {})
|
nested_config = nested_config.get(nested_key, {})
|
||||||
nested_config[nested_keys[-1]] = "REDACTED"
|
if nested_keys[-1] in nested_config:
|
||||||
|
nested_config[nested_keys[-1]] = "REDACTED"
|
||||||
else:
|
else:
|
||||||
config[key] = "REDACTED"
|
if key in config:
|
||||||
|
config[key] = "REDACTED"
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
|
|
||||||
@@ -11,8 +11,8 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def setup_utils_configuration(
|
def setup_utils_configuration(
|
||||||
args: Dict[str, Any], method: RunMode, *, set_dry: bool = True
|
args: dict[str, Any], method: RunMode, *, set_dry: bool = True
|
||||||
) -> Dict[str, Any]:
|
) -> 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()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from jsonschema import Draft4Validator, validators
|
from jsonschema import Draft4Validator, validators
|
||||||
from jsonschema.exceptions import ValidationError, best_match
|
from jsonschema.exceptions import ValidationError, best_match
|
||||||
@@ -43,7 +43,7 @@ def _extend_validator(validator_class):
|
|||||||
FreqtradeValidator = _extend_validator(Draft4Validator)
|
FreqtradeValidator = _extend_validator(Draft4Validator)
|
||||||
|
|
||||||
|
|
||||||
def validate_config_schema(conf: Dict[str, Any], preliminary: bool = False) -> Dict[str, Any]:
|
def validate_config_schema(conf: dict[str, Any], preliminary: bool = False) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Validate the configuration follow the Config Schema
|
Validate the configuration follow the Config Schema
|
||||||
:param conf: Config in JSON format
|
:param conf: Config in JSON format
|
||||||
@@ -69,7 +69,7 @@ def validate_config_schema(conf: Dict[str, Any], preliminary: bool = False) -> D
|
|||||||
raise ValidationError(best_match(Draft4Validator(conf_schema).iter_errors(conf)).message)
|
raise ValidationError(best_match(Draft4Validator(conf_schema).iter_errors(conf)).message)
|
||||||
|
|
||||||
|
|
||||||
def validate_config_consistency(conf: Dict[str, Any], *, preliminary: bool = False) -> None:
|
def validate_config_consistency(conf: dict[str, Any], *, preliminary: bool = False) -> None:
|
||||||
"""
|
"""
|
||||||
Validate the configuration consistency.
|
Validate the configuration consistency.
|
||||||
Should be ran after loading both configuration and strategy,
|
Should be ran after loading both configuration and strategy,
|
||||||
@@ -83,7 +83,6 @@ def validate_config_consistency(conf: Dict[str, Any], *, preliminary: bool = Fal
|
|||||||
_validate_price_config(conf)
|
_validate_price_config(conf)
|
||||||
_validate_edge(conf)
|
_validate_edge(conf)
|
||||||
_validate_whitelist(conf)
|
_validate_whitelist(conf)
|
||||||
_validate_protections(conf)
|
|
||||||
_validate_unlimited_amount(conf)
|
_validate_unlimited_amount(conf)
|
||||||
_validate_ask_orderbook(conf)
|
_validate_ask_orderbook(conf)
|
||||||
_validate_freqai_hyperopt(conf)
|
_validate_freqai_hyperopt(conf)
|
||||||
@@ -98,7 +97,7 @@ def validate_config_consistency(conf: Dict[str, Any], *, preliminary: bool = Fal
|
|||||||
validate_config_schema(conf, preliminary=preliminary)
|
validate_config_schema(conf, preliminary=preliminary)
|
||||||
|
|
||||||
|
|
||||||
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: ConfigurationError if config validation failed
|
:raise: ConfigurationError if config validation failed
|
||||||
@@ -111,7 +110,7 @@ def _validate_unlimited_amount(conf: Dict[str, Any]) -> None:
|
|||||||
raise ConfigurationError("`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:
|
||||||
"""
|
"""
|
||||||
When using market orders, price sides must be using the "other" side of the price
|
When using market orders, price sides must be using the "other" side of the price
|
||||||
"""
|
"""
|
||||||
@@ -127,7 +126,7 @@ def _validate_price_config(conf: Dict[str, Any]) -> None:
|
|||||||
raise ConfigurationError('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 ConfigurationError(
|
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."
|
||||||
@@ -160,7 +159,7 @@ def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _validate_edge(conf: Dict[str, Any]) -> None:
|
def _validate_edge(conf: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Edge and Dynamic whitelist should not both be enabled, since edge overrides dynamic whitelists.
|
Edge and Dynamic whitelist should not both be enabled, since edge overrides dynamic whitelists.
|
||||||
"""
|
"""
|
||||||
@@ -174,7 +173,7 @@ def _validate_edge(conf: Dict[str, Any]) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _validate_whitelist(conf: Dict[str, Any]) -> None:
|
def _validate_whitelist(conf: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Dynamic whitelist does not require pair_whitelist to be set - however StaticWhitelist does.
|
Dynamic whitelist does not require pair_whitelist to be set - however StaticWhitelist does.
|
||||||
"""
|
"""
|
||||||
@@ -195,26 +194,7 @@ def _validate_whitelist(conf: Dict[str, Any]) -> None:
|
|||||||
raise ConfigurationError("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_ask_orderbook(conf: dict[str, Any]) -> None:
|
||||||
"""
|
|
||||||
Validate protection configuration validity
|
|
||||||
"""
|
|
||||||
|
|
||||||
for prot in conf.get("protections", []):
|
|
||||||
if "stop_duration" in prot and "stop_duration_candles" in prot:
|
|
||||||
raise ConfigurationError(
|
|
||||||
"Protections must specify either `stop_duration` or `stop_duration_candles`.\n"
|
|
||||||
f"Please fix the protection {prot.get('method')}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if "lookback_period" in prot and "lookback_period_candles" in prot:
|
|
||||||
raise ConfigurationError(
|
|
||||||
"Protections must specify either `lookback_period` or `lookback_period_candles`.\n"
|
|
||||||
f"Please fix the protection {prot.get('method')}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_ask_orderbook(conf: Dict[str, Any]) -> None:
|
|
||||||
ask_strategy = conf.get("exit_pricing", {})
|
ask_strategy = conf.get("exit_pricing", {})
|
||||||
ob_min = ask_strategy.get("order_book_min")
|
ob_min = ask_strategy.get("order_book_min")
|
||||||
ob_max = ask_strategy.get("order_book_max")
|
ob_max = ask_strategy.get("order_book_max")
|
||||||
@@ -234,7 +214,7 @@ def _validate_ask_orderbook(conf: Dict[str, Any]) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate_migrated_strategy_settings(conf: Dict[str, Any]) -> None:
|
def validate_migrated_strategy_settings(conf: dict[str, Any]) -> None:
|
||||||
_validate_time_in_force(conf)
|
_validate_time_in_force(conf)
|
||||||
_validate_order_types(conf)
|
_validate_order_types(conf)
|
||||||
_validate_unfilledtimeout(conf)
|
_validate_unfilledtimeout(conf)
|
||||||
@@ -242,7 +222,7 @@ def validate_migrated_strategy_settings(conf: Dict[str, Any]) -> None:
|
|||||||
_strategy_settings(conf)
|
_strategy_settings(conf)
|
||||||
|
|
||||||
|
|
||||||
def _validate_time_in_force(conf: Dict[str, Any]) -> None:
|
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:
|
||||||
@@ -263,7 +243,7 @@ def _validate_time_in_force(conf: Dict[str, Any]) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _validate_order_types(conf: Dict[str, Any]) -> None:
|
def _validate_order_types(conf: dict[str, Any]) -> None:
|
||||||
order_types = conf.get("order_types", {})
|
order_types = conf.get("order_types", {})
|
||||||
old_order_types = [
|
old_order_types = [
|
||||||
"buy",
|
"buy",
|
||||||
@@ -298,7 +278,7 @@ def _validate_order_types(conf: Dict[str, Any]) -> None:
|
|||||||
process_deprecated_setting(conf, "order_types", o, "order_types", n)
|
process_deprecated_setting(conf, "order_types", o, "order_types", n)
|
||||||
|
|
||||||
|
|
||||||
def _validate_unfilledtimeout(conf: Dict[str, Any]) -> None:
|
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:
|
||||||
@@ -317,7 +297,7 @@ def _validate_unfilledtimeout(conf: Dict[str, Any]) -> None:
|
|||||||
process_deprecated_setting(conf, "unfilledtimeout", o, "unfilledtimeout", n)
|
process_deprecated_setting(conf, "unfilledtimeout", o, "unfilledtimeout", n)
|
||||||
|
|
||||||
|
|
||||||
def _validate_pricing_rules(conf: Dict[str, Any]) -> None:
|
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 ConfigurationError("Please migrate your pricing settings to use the new wording.")
|
raise ConfigurationError("Please migrate your pricing settings to use the new wording.")
|
||||||
@@ -347,7 +327,7 @@ def _validate_pricing_rules(conf: Dict[str, Any]) -> None:
|
|||||||
del conf["ask_strategy"]
|
del conf["ask_strategy"]
|
||||||
|
|
||||||
|
|
||||||
def _validate_freqai_hyperopt(conf: Dict[str, Any]) -> None:
|
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:
|
||||||
@@ -356,7 +336,7 @@ def _validate_freqai_hyperopt(conf: Dict[str, Any]) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _validate_freqai_include_timeframes(conf: Dict[str, Any], preliminary: bool) -> None:
|
def _validate_freqai_include_timeframes(conf: dict[str, Any], preliminary: bool) -> None:
|
||||||
freqai_enabled = conf.get("freqai", {}).get("enabled", False)
|
freqai_enabled = conf.get("freqai", {}).get("enabled", False)
|
||||||
if freqai_enabled:
|
if freqai_enabled:
|
||||||
main_tf = conf.get("timeframe", "5m")
|
main_tf = conf.get("timeframe", "5m")
|
||||||
@@ -387,7 +367,7 @@ def _validate_freqai_include_timeframes(conf: Dict[str, Any], preliminary: bool)
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _validate_freqai_backtest(conf: Dict[str, Any]) -> None:
|
def _validate_freqai_backtest(conf: dict[str, Any]) -> None:
|
||||||
if conf.get("runmode", RunMode.OTHER) == RunMode.BACKTEST:
|
if conf.get("runmode", RunMode.OTHER) == RunMode.BACKTEST:
|
||||||
freqai_enabled = conf.get("freqai", {}).get("enabled", False)
|
freqai_enabled = conf.get("freqai", {}).get("enabled", False)
|
||||||
timerange = conf.get("timerange")
|
timerange = conf.get("timerange")
|
||||||
@@ -410,7 +390,7 @@ def _validate_freqai_backtest(conf: Dict[str, Any]) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _validate_consumers(conf: Dict[str, Any]) -> None:
|
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:
|
||||||
@@ -430,7 +410,7 @@ def _validate_consumers(conf: Dict[str, Any]) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _validate_orderflow(conf: Dict[str, Any]) -> None:
|
def _validate_orderflow(conf: dict[str, Any]) -> None:
|
||||||
if conf.get("exchange", {}).get("use_public_trades"):
|
if conf.get("exchange", {}).get("use_public_trades"):
|
||||||
if "orderflow" not in conf:
|
if "orderflow" not in conf:
|
||||||
raise ConfigurationError(
|
raise ConfigurationError(
|
||||||
@@ -438,7 +418,7 @@ def _validate_orderflow(conf: Dict[str, Any]) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _strategy_settings(conf: Dict[str, Any]) -> None:
|
def _strategy_settings(conf: dict[str, Any]) -> None:
|
||||||
process_deprecated_setting(conf, None, "use_sell_signal", None, "use_exit_signal")
|
process_deprecated_setting(conf, None, "use_sell_signal", None, "use_exit_signal")
|
||||||
process_deprecated_setting(conf, None, "sell_profit_only", None, "exit_profit_only")
|
process_deprecated_setting(conf, None, "sell_profit_only", None, "exit_profit_only")
|
||||||
process_deprecated_setting(conf, None, "sell_profit_offset", None, "exit_profit_offset")
|
process_deprecated_setting(conf, None, "sell_profit_offset", None, "exit_profit_offset")
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ This module contains the configuration class
|
|||||||
import ast
|
import ast
|
||||||
import logging
|
import logging
|
||||||
import warnings
|
import warnings
|
||||||
|
from collections.abc import Callable
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
from typing import Any
|
||||||
|
|
||||||
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
|
||||||
@@ -15,7 +16,14 @@ 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, TRADE_MODES, CandleType, RunMode, TradingMode
|
from freqtrade.enums import (
|
||||||
|
NON_UTIL_MODES,
|
||||||
|
TRADE_MODES,
|
||||||
|
CandleType,
|
||||||
|
MarginMode,
|
||||||
|
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
|
||||||
@@ -30,9 +38,9 @@ class Configuration:
|
|||||||
Reuse this class for the bot, backtesting, hyperopt and every script that required configuration
|
Reuse this class for the bot, backtesting, hyperopt and every script that required configuration
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, args: Dict[str, Any], runmode: Optional[RunMode] = None) -> None:
|
def __init__(self, args: dict[str, Any], runmode: RunMode | None = None) -> None:
|
||||||
self.args = args
|
self.args = args
|
||||||
self.config: Optional[Config] = None
|
self.config: Config | None = None
|
||||||
self.runmode = runmode
|
self.runmode = runmode
|
||||||
|
|
||||||
def get_config(self) -> Config:
|
def get_config(self) -> Config:
|
||||||
@@ -46,7 +54,7 @@ class Configuration:
|
|||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_files(files: List[str]) -> Dict[str, Any]:
|
def from_files(files: list[str]) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Iterate through the config files passed in, loading all of them
|
Iterate through the config files passed in, loading all of them
|
||||||
and merging their contents.
|
and merging their contents.
|
||||||
@@ -61,7 +69,7 @@ class Configuration:
|
|||||||
c = Configuration({"config": files}, RunMode.OTHER)
|
c = Configuration({"config": files}, RunMode.OTHER)
|
||||||
return c.get_config()
|
return c.get_config()
|
||||||
|
|
||||||
def load_config(self) -> Dict[str, Any]:
|
def load_config(self) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Extract information for sys.argv and load the bot configuration
|
Extract information for sys.argv and load the bot configuration
|
||||||
:return: Configuration dictionary
|
:return: Configuration dictionary
|
||||||
@@ -234,11 +242,7 @@ class Configuration:
|
|||||||
logstring="Parameter --enable-protections detected, enabling Protections. ...",
|
logstring="Parameter --enable-protections detected, enabling Protections. ...",
|
||||||
)
|
)
|
||||||
|
|
||||||
if "use_max_market_positions" in self.args and not self.args["use_max_market_positions"]:
|
if "max_open_trades" in self.args and self.args["max_open_trades"]:
|
||||||
config.update({"use_max_market_positions": False})
|
|
||||||
logger.info("Parameter --disable-max-market-positions detected ...")
|
|
||||||
logger.info("max_open_trades set to unlimited ...")
|
|
||||||
elif "max_open_trades" in self.args and self.args["max_open_trades"]:
|
|
||||||
config.update({"max_open_trades": self.args["max_open_trades"]})
|
config.update({"max_open_trades": self.args["max_open_trades"]})
|
||||||
logger.info(
|
logger.info(
|
||||||
"Parameter --max-open-trades detected, overriding max_open_trades to: %s ...",
|
"Parameter --max-open-trades detected, overriding max_open_trades to: %s ...",
|
||||||
@@ -389,6 +393,7 @@ class Configuration:
|
|||||||
config.get("trading_mode", "spot") or "spot"
|
config.get("trading_mode", "spot") or "spot"
|
||||||
)
|
)
|
||||||
config["trading_mode"] = TradingMode(config.get("trading_mode", "spot") or "spot")
|
config["trading_mode"] = TradingMode(config.get("trading_mode", "spot") or "spot")
|
||||||
|
config["margin_mode"] = MarginMode(config.get("margin_mode", "") or "")
|
||||||
self._args_to_config(
|
self._args_to_config(
|
||||||
config, argname="candle_types", logstring="Detected --candle-types: {}"
|
config, argname="candle_types", logstring="Detected --candle-types: {}"
|
||||||
)
|
)
|
||||||
@@ -399,6 +404,8 @@ class Configuration:
|
|||||||
("enter_reason_list", "Analysis enter tag list: {}"),
|
("enter_reason_list", "Analysis enter tag list: {}"),
|
||||||
("exit_reason_list", "Analysis exit tag list: {}"),
|
("exit_reason_list", "Analysis exit tag list: {}"),
|
||||||
("indicator_list", "Analysis indicator list: {}"),
|
("indicator_list", "Analysis indicator list: {}"),
|
||||||
|
("entry_only", "Only analyze entry signals: {}"),
|
||||||
|
("exit_only", "Only analyze exit signals: {}"),
|
||||||
("timerange", "Filter trades by timerange: {}"),
|
("timerange", "Filter trades by timerange: {}"),
|
||||||
("analysis_rejected", "Analyse rejected signals: {}"),
|
("analysis_rejected", "Analyse rejected signals: {}"),
|
||||||
("analysis_to_csv", "Store analysis tables to CSV: {}"),
|
("analysis_to_csv", "Store analysis tables to CSV: {}"),
|
||||||
@@ -411,7 +418,7 @@ class Configuration:
|
|||||||
]
|
]
|
||||||
self._args_to_config_loop(config, configurations)
|
self._args_to_config_loop(config, configurations)
|
||||||
|
|
||||||
def _args_to_config_loop(self, config, configurations: List[Tuple[str, str]]) -> None:
|
def _args_to_config_loop(self, config, configurations: list[tuple[str, str]]) -> None:
|
||||||
for argname, logstring in configurations:
|
for argname, logstring in configurations:
|
||||||
self._args_to_config(config, argname=argname, logstring=logstring)
|
self._args_to_config(config, argname=argname, logstring=logstring)
|
||||||
|
|
||||||
@@ -445,8 +452,8 @@ class Configuration:
|
|||||||
config: Config,
|
config: Config,
|
||||||
argname: str,
|
argname: str,
|
||||||
logstring: str,
|
logstring: str,
|
||||||
logfun: Optional[Callable] = None,
|
logfun: Callable | None = None,
|
||||||
deprecated_msg: Optional[str] = None,
|
deprecated_msg: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
:param config: Configuration dictionary
|
:param config: Configuration dictionary
|
||||||
@@ -468,7 +475,7 @@ class Configuration:
|
|||||||
else:
|
else:
|
||||||
logger.info(logstring.format(config[argname]))
|
logger.info(logstring.format(config[argname]))
|
||||||
if deprecated_msg:
|
if deprecated_msg:
|
||||||
warnings.warn(f"DEPRECATED: {deprecated_msg}", DeprecationWarning)
|
warnings.warn(f"DEPRECATED: {deprecated_msg}", DeprecationWarning, stacklevel=1)
|
||||||
|
|
||||||
def _resolve_pairs_list(self, config: Config) -> None:
|
def _resolve_pairs_list(self, config: Config) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -0,0 +1,250 @@
|
|||||||
|
import logging
|
||||||
|
import secrets
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from questionary import Separator, prompt
|
||||||
|
|
||||||
|
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
|
||||||
|
from freqtrade.exceptions import OperationalException
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_is_int(val):
|
||||||
|
try:
|
||||||
|
_ = int(val)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def validate_is_float(val):
|
||||||
|
try:
|
||||||
|
_ = float(val)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def ask_user_overwrite(config_path: Path) -> bool:
|
||||||
|
questions = [
|
||||||
|
{
|
||||||
|
"type": "confirm",
|
||||||
|
"name": "overwrite",
|
||||||
|
"message": f"File {config_path} already exists. Overwrite?",
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
answers = prompt(questions)
|
||||||
|
return answers["overwrite"]
|
||||||
|
|
||||||
|
|
||||||
|
def ask_user_config() -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Ask user a few questions to build the configuration.
|
||||||
|
Interactive questions built using https://github.com/tmbo/questionary
|
||||||
|
:returns: Dict with keys to put into template
|
||||||
|
"""
|
||||||
|
|
||||||
|
from freqtrade.configuration.detect_environment import running_in_docker
|
||||||
|
from freqtrade.exchange import available_exchanges
|
||||||
|
|
||||||
|
questions: list[dict[str, Any]] = [
|
||||||
|
{
|
||||||
|
"type": "confirm",
|
||||||
|
"name": "dry_run",
|
||||||
|
"message": "Do you want to enable Dry-run (simulated trades)?",
|
||||||
|
"default": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"name": "stake_currency",
|
||||||
|
"message": "Please insert your stake currency:",
|
||||||
|
"default": "USDT",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"name": "stake_amount",
|
||||||
|
"message": f"Please insert your stake amount (Number or '{UNLIMITED_STAKE_AMOUNT}'):",
|
||||||
|
"default": "unlimited",
|
||||||
|
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val),
|
||||||
|
"filter": lambda val: (
|
||||||
|
'"' + UNLIMITED_STAKE_AMOUNT + '"' if val == UNLIMITED_STAKE_AMOUNT else val
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"name": "max_open_trades",
|
||||||
|
"message": "Please insert max_open_trades (Integer or -1 for unlimited open trades):",
|
||||||
|
"default": "3",
|
||||||
|
"validate": lambda val: validate_is_int(val),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"name": "timeframe_in_config",
|
||||||
|
"message": "Time",
|
||||||
|
"choices": ["Have the strategy define timeframe.", "Override in configuration."],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"name": "timeframe",
|
||||||
|
"message": "Please insert your desired timeframe (e.g. 5m):",
|
||||||
|
"default": "5m",
|
||||||
|
"when": lambda x: x["timeframe_in_config"] == "Override in configuration.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"name": "fiat_display_currency",
|
||||||
|
"message": (
|
||||||
|
"Please insert your display Currency for reporting "
|
||||||
|
"(leave empty to disable FIAT conversion):"
|
||||||
|
),
|
||||||
|
"default": "USD",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"name": "exchange_name",
|
||||||
|
"message": "Select exchange",
|
||||||
|
"choices": [
|
||||||
|
"binance",
|
||||||
|
"binanceus",
|
||||||
|
"bingx",
|
||||||
|
"gate",
|
||||||
|
"htx",
|
||||||
|
"kraken",
|
||||||
|
"kucoin",
|
||||||
|
"okx",
|
||||||
|
Separator("------------------"),
|
||||||
|
"other",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "confirm",
|
||||||
|
"name": "trading_mode",
|
||||||
|
"message": "Do you want to trade Perpetual Swaps (perpetual futures)?",
|
||||||
|
"default": False,
|
||||||
|
"filter": lambda val: "futures" if val else "spot",
|
||||||
|
"when": lambda x: x["exchange_name"] in ["binance", "gate", "okx", "bybit"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "autocomplete",
|
||||||
|
"name": "exchange_name",
|
||||||
|
"message": "Type your exchange name (Must be supported by ccxt)",
|
||||||
|
"choices": available_exchanges(),
|
||||||
|
"when": lambda x: x["exchange_name"] == "other",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "password",
|
||||||
|
"name": "exchange_key",
|
||||||
|
"message": "Insert Exchange Key",
|
||||||
|
"when": lambda x: not x["dry_run"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "password",
|
||||||
|
"name": "exchange_secret",
|
||||||
|
"message": "Insert Exchange Secret",
|
||||||
|
"when": lambda x: not x["dry_run"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "password",
|
||||||
|
"name": "exchange_key_password",
|
||||||
|
"message": "Insert Exchange API Key password",
|
||||||
|
"when": lambda x: not x["dry_run"] and x["exchange_name"] in ("kucoin", "okx"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "confirm",
|
||||||
|
"name": "telegram",
|
||||||
|
"message": "Do you want to enable Telegram?",
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "password",
|
||||||
|
"name": "telegram_token",
|
||||||
|
"message": "Insert Telegram token",
|
||||||
|
"when": lambda x: x["telegram"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "password",
|
||||||
|
"name": "telegram_chat_id",
|
||||||
|
"message": "Insert Telegram chat id",
|
||||||
|
"when": lambda x: x["telegram"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "confirm",
|
||||||
|
"name": "api_server",
|
||||||
|
"message": "Do you want to enable the Rest API (includes FreqUI)?",
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"name": "api_server_listen_addr",
|
||||||
|
"message": (
|
||||||
|
"Insert Api server Listen Address (0.0.0.0 for docker, "
|
||||||
|
"otherwise best left untouched)"
|
||||||
|
),
|
||||||
|
"default": "127.0.0.1" if not running_in_docker() else "0.0.0.0", # noqa: S104
|
||||||
|
"when": lambda x: x["api_server"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"name": "api_server_username",
|
||||||
|
"message": "Insert api-server username",
|
||||||
|
"default": "freqtrader",
|
||||||
|
"when": lambda x: x["api_server"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "password",
|
||||||
|
"name": "api_server_password",
|
||||||
|
"message": "Insert api-server password",
|
||||||
|
"when": lambda x: x["api_server"],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
answers = prompt(questions)
|
||||||
|
|
||||||
|
if not answers:
|
||||||
|
# Interrupted questionary sessions return an empty dict.
|
||||||
|
raise OperationalException("User interrupted interactive questions.")
|
||||||
|
# Ensure default is set for non-futures exchanges
|
||||||
|
answers["trading_mode"] = answers.get("trading_mode", "spot")
|
||||||
|
answers["margin_mode"] = "isolated" if answers.get("trading_mode") == "futures" else ""
|
||||||
|
# Force JWT token to be a random string
|
||||||
|
answers["api_server_jwt_key"] = secrets.token_hex()
|
||||||
|
answers["api_server_ws_token"] = secrets.token_urlsafe(25)
|
||||||
|
|
||||||
|
return answers
|
||||||
|
|
||||||
|
|
||||||
|
def deploy_new_config(config_path: Path, selections: dict[str, Any]) -> None:
|
||||||
|
"""
|
||||||
|
Applies selections to the template and writes the result to config_path
|
||||||
|
:param config_path: Path object for new config file. Should not exist yet
|
||||||
|
:param selections: Dict containing selections taken by the user.
|
||||||
|
"""
|
||||||
|
from jinja2.exceptions import TemplateNotFound
|
||||||
|
|
||||||
|
from freqtrade.exchange import MAP_EXCHANGE_CHILDCLASS
|
||||||
|
from freqtrade.util import render_template
|
||||||
|
|
||||||
|
try:
|
||||||
|
exchange_template = MAP_EXCHANGE_CHILDCLASS.get(
|
||||||
|
selections["exchange_name"], selections["exchange_name"]
|
||||||
|
)
|
||||||
|
|
||||||
|
selections["exchange"] = render_template(
|
||||||
|
templatefile=f"subtemplates/exchange_{exchange_template}.j2", arguments=selections
|
||||||
|
)
|
||||||
|
except TemplateNotFound:
|
||||||
|
selections["exchange"] = render_template(
|
||||||
|
templatefile="subtemplates/exchange_generic.j2", arguments=selections
|
||||||
|
)
|
||||||
|
|
||||||
|
config_text = render_template(templatefile="base_config.json.j2", arguments=selections)
|
||||||
|
|
||||||
|
logger.info(f"Writing config to `{config_path}`.")
|
||||||
|
logger.info(
|
||||||
|
"Please make sure to check the configuration contents and adjust settings to your needs."
|
||||||
|
)
|
||||||
|
|
||||||
|
config_path.write_text(config_text)
|
||||||
@@ -3,7 +3,6 @@ Functions to handle deprecated settings
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from freqtrade.constants import Config
|
from freqtrade.constants import Config
|
||||||
from freqtrade.exceptions import ConfigurationError, OperationalException
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||||
@@ -14,9 +13,9 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
def check_conflicting_settings(
|
def check_conflicting_settings(
|
||||||
config: Config,
|
config: Config,
|
||||||
section_old: Optional[str],
|
section_old: str | None,
|
||||||
name_old: str,
|
name_old: str,
|
||||||
section_new: Optional[str],
|
section_new: str | None,
|
||||||
name_new: str,
|
name_new: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
section_new_config = config.get(section_new, {}) if section_new else config
|
section_new_config = config.get(section_new, {}) if section_new else config
|
||||||
@@ -34,7 +33,7 @@ def check_conflicting_settings(
|
|||||||
|
|
||||||
|
|
||||||
def process_removed_setting(
|
def process_removed_setting(
|
||||||
config: Config, section1: str, name1: str, section2: Optional[str], name2: str
|
config: Config, section1: str, name1: str, section2: str | None, name2: str
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
:param section1: Removed section
|
:param section1: Removed section
|
||||||
@@ -54,9 +53,9 @@ def process_removed_setting(
|
|||||||
|
|
||||||
def process_deprecated_setting(
|
def process_deprecated_setting(
|
||||||
config: Config,
|
config: Config,
|
||||||
section_old: Optional[str],
|
section_old: str | None,
|
||||||
name_old: str,
|
name_old: str,
|
||||||
section_new: Optional[str],
|
section_new: str | None,
|
||||||
name_new: str,
|
name_new: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
check_conflicting_settings(config, section_old, name_old, section_new, name_new)
|
check_conflicting_settings(config, section_old, name_old, section_new, name_new)
|
||||||
@@ -177,4 +176,6 @@ def process_temporary_deprecated_settings(config: Config) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if "protections" in config:
|
if "protections" in config:
|
||||||
logger.warning("DEPRECATED: Setting 'protections' in the configuration is deprecated.")
|
raise ConfigurationError(
|
||||||
|
"DEPRECATED: Setting 'protections' in the configuration is deprecated."
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import shutil
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from freqtrade.configuration.detect_environment import running_in_docker
|
from freqtrade.configuration.detect_environment import running_in_docker
|
||||||
from freqtrade.constants import (
|
from freqtrade.constants import (
|
||||||
@@ -18,7 +17,7 @@ from freqtrade.exceptions import OperationalException
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def create_datadir(config: Config, datadir: Optional[str] = None) -> Path:
|
def create_datadir(config: Config, datadir: str | None = None) -> Path:
|
||||||
folder = Path(datadir) if datadir else Path(f"{config['user_data_dir']}/data")
|
folder = Path(datadir) if datadir else Path(f"{config['user_data_dir']}/data")
|
||||||
if not datadir:
|
if not datadir:
|
||||||
# set datadir
|
# set datadir
|
||||||
@@ -82,6 +81,11 @@ def create_userdata_dir(directory: str, create_dir: bool = False) -> Path:
|
|||||||
for f in sub_dirs:
|
for f in sub_dirs:
|
||||||
subfolder = folder / f
|
subfolder = folder / f
|
||||||
if not subfolder.is_dir():
|
if not subfolder.is_dir():
|
||||||
|
if subfolder.exists() or subfolder.is_symlink():
|
||||||
|
raise OperationalException(
|
||||||
|
f"File `{subfolder}` exists already and is not a directory. "
|
||||||
|
"Freqtrade requires this to be a directory."
|
||||||
|
)
|
||||||
subfolder.mkdir(parents=False)
|
subfolder.mkdir(parents=False)
|
||||||
return folder
|
return folder
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
|
import rapidjson
|
||||||
|
|
||||||
from freqtrade.constants import ENV_VAR_PREFIX
|
from freqtrade.constants import ENV_VAR_PREFIX
|
||||||
from freqtrade.misc import deep_merge_dicts
|
from freqtrade.misc import deep_merge_dicts
|
||||||
@@ -20,11 +22,19 @@ def _get_var_typed(val):
|
|||||||
return True
|
return True
|
||||||
elif val.lower() in ("f", "false"):
|
elif val.lower() in ("f", "false"):
|
||||||
return False
|
return False
|
||||||
|
# try to convert from json
|
||||||
|
try:
|
||||||
|
value = rapidjson.loads(val)
|
||||||
|
# Limited to lists for now
|
||||||
|
if isinstance(value, list):
|
||||||
|
return value
|
||||||
|
except rapidjson.JSONDecodeError:
|
||||||
|
pass
|
||||||
# keep as string
|
# keep as string
|
||||||
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}
|
||||||
@@ -33,7 +43,7 @@ def _flat_vars_to_nested_dict(env_dict: Dict[str, Any], prefix: str) -> Dict[str
|
|||||||
:return: Nested dict based on available and relevant variables.
|
:return: Nested dict based on available and relevant variables.
|
||||||
"""
|
"""
|
||||||
no_convert = ["CHAT_ID", "PASSWORD"]
|
no_convert = ["CHAT_ID", "PASSWORD"]
|
||||||
relevant_vars: Dict[str, Any] = {}
|
relevant_vars: dict[str, Any] = {}
|
||||||
|
|
||||||
for env_var, val in sorted(env_dict.items()):
|
for env_var, val in sorted(env_dict.items()):
|
||||||
if env_var.startswith(prefix):
|
if env_var.startswith(prefix):
|
||||||
@@ -51,7 +61,7 @@ def _flat_vars_to_nested_dict(env_dict: Dict[str, Any], prefix: str) -> Dict[str
|
|||||||
return relevant_vars
|
return relevant_vars
|
||||||
|
|
||||||
|
|
||||||
def enironment_vars_to_dict() -> Dict[str, Any]:
|
def enironment_vars_to_dict() -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Read environment variables and return a nested dict for relevant variables
|
Read environment variables and return a nested dict for relevant variables
|
||||||
Relevant variables must follow the FREQTRADE__{section}__{key} pattern
|
Relevant variables must follow the FREQTRADE__{section}__{key} pattern
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any
|
||||||
|
|
||||||
import rapidjson
|
import rapidjson
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ def log_config_error_range(path: str, errmsg: str) -> str:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def load_file(path: Path) -> Dict[str, Any]:
|
def load_file(path: Path) -> dict[str, Any]:
|
||||||
try:
|
try:
|
||||||
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)
|
||||||
@@ -51,7 +51,7 @@ def load_file(path: Path) -> Dict[str, Any]:
|
|||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
def load_config_file(path: str) -> Dict[str, Any]:
|
def load_config_file(path: str) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Loads a config file from the given path
|
Loads a config file from the given path
|
||||||
:param path: path as str
|
:param path: path as str
|
||||||
@@ -78,8 +78,8 @@ def load_config_file(path: str) -> Dict[str, Any]:
|
|||||||
|
|
||||||
|
|
||||||
def load_from_files(
|
def load_from_files(
|
||||||
files: List[str], base_path: Optional[Path] = None, level: int = 0
|
files: list[str], base_path: Path | None = None, level: int = 0
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Recursively load configuration files if specified.
|
Recursively load configuration files if specified.
|
||||||
Sub-files are assumed to be relative to the initial config.
|
Sub-files are assumed to be relative to the initial config.
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ This module contains the argument manager class
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
@@ -25,24 +24,24 @@ class TimeRange:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
starttype: Optional[str] = None,
|
starttype: str | None = None,
|
||||||
stoptype: Optional[str] = None,
|
stoptype: str | None = None,
|
||||||
startts: int = 0,
|
startts: int = 0,
|
||||||
stopts: int = 0,
|
stopts: int = 0,
|
||||||
):
|
):
|
||||||
self.starttype: Optional[str] = starttype
|
self.starttype: str | None = starttype
|
||||||
self.stoptype: Optional[str] = stoptype
|
self.stoptype: str | None = stoptype
|
||||||
self.startts: int = startts
|
self.startts: int = startts
|
||||||
self.stopts: int = stopts
|
self.stopts: int = stopts
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def startdt(self) -> Optional[datetime]:
|
def startdt(self) -> datetime | None:
|
||||||
if self.startts:
|
if self.startts:
|
||||||
return datetime.fromtimestamp(self.startts, tz=timezone.utc)
|
return datetime.fromtimestamp(self.startts, tz=timezone.utc)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def stopdt(self) -> Optional[datetime]:
|
def stopdt(self) -> datetime | None:
|
||||||
if self.stopts:
|
if self.stopts:
|
||||||
return datetime.fromtimestamp(self.stopts, tz=timezone.utc)
|
return datetime.fromtimestamp(self.stopts, tz=timezone.utc)
|
||||||
return None
|
return None
|
||||||
@@ -120,7 +119,7 @@ class TimeRange:
|
|||||||
self.starttype = "date"
|
self.starttype = "date"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_timerange(cls, text: Optional[str]) -> Self:
|
def parse_timerange(cls, text: str | None) -> Self:
|
||||||
"""
|
"""
|
||||||
Parse the value of the argument --timerange to determine what is the range desired
|
Parse the value of the argument --timerange to determine what is the range desired
|
||||||
:param text: value from --timerange
|
:param text: value from --timerange
|
||||||
|
|||||||
+11
-10
@@ -4,7 +4,7 @@
|
|||||||
bot constants
|
bot constants
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, Dict, List, Literal, Optional, Tuple
|
from typing import Any, Literal
|
||||||
|
|
||||||
from freqtrade.enums import CandleType, PriceType
|
from freqtrade.enums import CandleType, PriceType
|
||||||
|
|
||||||
@@ -38,10 +38,12 @@ HYPEROPT_LOSS_BUILTIN = [
|
|||||||
"MaxDrawDownHyperOptLoss",
|
"MaxDrawDownHyperOptLoss",
|
||||||
"MaxDrawDownRelativeHyperOptLoss",
|
"MaxDrawDownRelativeHyperOptLoss",
|
||||||
"ProfitDrawDownHyperOptLoss",
|
"ProfitDrawDownHyperOptLoss",
|
||||||
|
"MultiMetricHyperOptLoss",
|
||||||
]
|
]
|
||||||
AVAILABLE_PAIRLISTS = [
|
AVAILABLE_PAIRLISTS = [
|
||||||
"StaticPairList",
|
"StaticPairList",
|
||||||
"VolumePairList",
|
"VolumePairList",
|
||||||
|
"PercentChangePairList",
|
||||||
"ProducerPairList",
|
"ProducerPairList",
|
||||||
"RemotePairList",
|
"RemotePairList",
|
||||||
"MarketCapPairList",
|
"MarketCapPairList",
|
||||||
@@ -56,7 +58,6 @@ AVAILABLE_PAIRLISTS = [
|
|||||||
"SpreadFilter",
|
"SpreadFilter",
|
||||||
"VolatilityFilter",
|
"VolatilityFilter",
|
||||||
]
|
]
|
||||||
AVAILABLE_PROTECTIONS = ["CooldownPeriod", "LowProfitPairs", "MaxDrawdown", "StoplossGuard"]
|
|
||||||
AVAILABLE_DATAHANDLERS = ["json", "jsongz", "hdf5", "feather", "parquet"]
|
AVAILABLE_DATAHANDLERS = ["json", "jsongz", "hdf5", "feather", "parquet"]
|
||||||
BACKTEST_BREAKDOWNS = ["day", "week", "month"]
|
BACKTEST_BREAKDOWNS = ["day", "week", "month"]
|
||||||
BACKTEST_CACHE_AGE = ["none", "day", "week", "month"]
|
BACKTEST_CACHE_AGE = ["none", "day", "week", "month"]
|
||||||
@@ -97,7 +98,7 @@ DL_DATA_TIMEFRAMES = ["1m", "5m"]
|
|||||||
|
|
||||||
ENV_VAR_PREFIX = "FREQTRADE__"
|
ENV_VAR_PREFIX = "FREQTRADE__"
|
||||||
|
|
||||||
CANCELED_EXCHANGE_STATES = ("cancelled", "canceled", "expired")
|
CANCELED_EXCHANGE_STATES = ("cancelled", "canceled", "expired", "rejected")
|
||||||
NON_OPEN_EXCHANGE_STATES = CANCELED_EXCHANGE_STATES + ("closed",)
|
NON_OPEN_EXCHANGE_STATES = CANCELED_EXCHANGE_STATES + ("closed",)
|
||||||
|
|
||||||
# Define decimals per coin for outputs
|
# Define decimals per coin for outputs
|
||||||
@@ -187,14 +188,14 @@ CANCEL_REASON = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# List of pairs with their timeframes
|
# List of pairs with their timeframes
|
||||||
PairWithTimeframe = Tuple[str, str, CandleType]
|
PairWithTimeframe = tuple[str, str, CandleType]
|
||||||
ListPairsWithTimeframes = List[PairWithTimeframe]
|
ListPairsWithTimeframes = list[PairWithTimeframe]
|
||||||
|
|
||||||
# Type for trades list
|
# Type for trades list
|
||||||
TradeList = List[List]
|
TradeList = list[list]
|
||||||
# ticks, pair, timeframe, CandleType
|
# ticks, pair, timeframe, CandleType
|
||||||
TickWithTimeframe = Tuple[str, str, CandleType, Optional[int], Optional[int]]
|
TickWithTimeframe = tuple[str, str, CandleType, int | None, int | None]
|
||||||
ListTicksWithTimeframes = List[TickWithTimeframe]
|
ListTicksWithTimeframes = list[TickWithTimeframe]
|
||||||
|
|
||||||
LongShort = Literal["long", "short"]
|
LongShort = Literal["long", "short"]
|
||||||
EntryExit = Literal["entry", "exit"]
|
EntryExit = Literal["entry", "exit"]
|
||||||
@@ -203,9 +204,9 @@ MakerTaker = Literal["maker", "taker"]
|
|||||||
BidAsk = Literal["bid", "ask"]
|
BidAsk = Literal["bid", "ask"]
|
||||||
OBLiteral = Literal["asks", "bids"]
|
OBLiteral = Literal["asks", "bids"]
|
||||||
|
|
||||||
Config = Dict[str, Any]
|
Config = dict[str, Any]
|
||||||
# Exchange part of the configuration.
|
# Exchange part of the configuration.
|
||||||
ExchangeConfig = Dict[str, Any]
|
ExchangeConfig = dict[str, Any]
|
||||||
IntOrInf = float
|
IntOrInf = float
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,17 +6,17 @@ import logging
|
|||||||
from copy import copy
|
from copy import copy
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Literal, Optional, Union
|
from typing import Any, Literal
|
||||||
|
|
||||||
import numpy as np
|
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 ConfigurationError, OperationalException
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||||
|
from freqtrade.ft_types import BacktestHistoryEntryType, BacktestResultType
|
||||||
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
|
||||||
from freqtrade.types import BacktestHistoryEntryType, BacktestResultType
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -53,7 +53,7 @@ BT_DATA_COLUMNS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str:
|
def get_latest_optimize_filename(directory: Path | str, variant: str) -> str:
|
||||||
"""
|
"""
|
||||||
Get latest backtest export based on '.last_result.json'.
|
Get latest backtest export based on '.last_result.json'.
|
||||||
:param directory: Directory to search for last result
|
:param directory: Directory to search for last result
|
||||||
@@ -84,7 +84,7 @@ def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> s
|
|||||||
return data[f"latest_{variant}"]
|
return data[f"latest_{variant}"]
|
||||||
|
|
||||||
|
|
||||||
def get_latest_backtest_filename(directory: Union[Path, str]) -> str:
|
def get_latest_backtest_filename(directory: Path | str) -> str:
|
||||||
"""
|
"""
|
||||||
Get latest backtest export based on '.last_result.json'.
|
Get latest backtest export based on '.last_result.json'.
|
||||||
:param directory: Directory to search for last result
|
:param directory: Directory to search for last result
|
||||||
@@ -97,7 +97,7 @@ def get_latest_backtest_filename(directory: Union[Path, str]) -> str:
|
|||||||
return get_latest_optimize_filename(directory, "backtest")
|
return get_latest_optimize_filename(directory, "backtest")
|
||||||
|
|
||||||
|
|
||||||
def get_latest_hyperopt_filename(directory: Union[Path, str]) -> str:
|
def get_latest_hyperopt_filename(directory: Path | str) -> str:
|
||||||
"""
|
"""
|
||||||
Get latest hyperopt export based on '.last_result.json'.
|
Get latest hyperopt export based on '.last_result.json'.
|
||||||
:param directory: Directory to search for last result
|
:param directory: Directory to search for last result
|
||||||
@@ -114,9 +114,7 @@ def get_latest_hyperopt_filename(directory: Union[Path, str]) -> str:
|
|||||||
return "hyperopt_results.pickle"
|
return "hyperopt_results.pickle"
|
||||||
|
|
||||||
|
|
||||||
def get_latest_hyperopt_file(
|
def get_latest_hyperopt_file(directory: Path | str, predef_filename: str | None = None) -> Path:
|
||||||
directory: Union[Path, str], predef_filename: Optional[str] = None
|
|
||||||
) -> Path:
|
|
||||||
"""
|
"""
|
||||||
Get latest hyperopt export based on '.last_result.json'.
|
Get latest hyperopt export based on '.last_result.json'.
|
||||||
:param directory: Directory to search for last result
|
:param directory: Directory to search for last result
|
||||||
@@ -137,7 +135,7 @@ def get_latest_hyperopt_file(
|
|||||||
return directory / get_latest_hyperopt_filename(directory)
|
return directory / get_latest_hyperopt_filename(directory)
|
||||||
|
|
||||||
|
|
||||||
def load_backtest_metadata(filename: Union[Path, str]) -> Dict[str, Any]:
|
def load_backtest_metadata(filename: Path | str) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Read metadata dictionary from backtest results file without reading and deserializing entire
|
Read metadata dictionary from backtest results file without reading and deserializing entire
|
||||||
file.
|
file.
|
||||||
@@ -154,7 +152,7 @@ def load_backtest_metadata(filename: Union[Path, str]) -> Dict[str, Any]:
|
|||||||
raise OperationalException("Unexpected error while loading backtest metadata.") from e
|
raise OperationalException("Unexpected error while loading backtest metadata.") from e
|
||||||
|
|
||||||
|
|
||||||
def load_backtest_stats(filename: Union[Path, str]) -> BacktestResultType:
|
def load_backtest_stats(filename: Path | str) -> BacktestResultType:
|
||||||
"""
|
"""
|
||||||
Load backtest statistics file.
|
Load backtest statistics file.
|
||||||
:param filename: pathlib.Path object, or string pointing to the file.
|
:param filename: pathlib.Path object, or string pointing to the file.
|
||||||
@@ -176,7 +174,7 @@ def load_backtest_stats(filename: Union[Path, str]) -> BacktestResultType:
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def load_and_merge_backtest_result(strategy_name: str, filename: Path, results: Dict[str, Any]):
|
def load_and_merge_backtest_result(strategy_name: str, filename: Path, results: dict[str, Any]):
|
||||||
"""
|
"""
|
||||||
Load one strategy from multi-strategy result and merge it with results
|
Load one strategy from multi-strategy result and merge it with results
|
||||||
:param strategy_name: Name of the strategy contained in the result
|
:param strategy_name: Name of the strategy contained in the result
|
||||||
@@ -195,12 +193,12 @@ def load_and_merge_backtest_result(strategy_name: str, filename: Path, results:
|
|||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def _get_backtest_files(dirname: Path) -> List[Path]:
|
def _get_backtest_files(dirname: Path) -> list[Path]:
|
||||||
# Weird glob expression here avoids including .meta.json files.
|
# Weird glob expression here avoids including .meta.json files.
|
||||||
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 _extract_backtest_result(filename: Path) -> List[BacktestHistoryEntryType]:
|
def _extract_backtest_result(filename: Path) -> list[BacktestHistoryEntryType]:
|
||||||
metadata = load_backtest_metadata(filename)
|
metadata = load_backtest_metadata(filename)
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -220,14 +218,14 @@ def _extract_backtest_result(filename: Path) -> List[BacktestHistoryEntryType]:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_backtest_result(filename: Path) -> List[BacktestHistoryEntryType]:
|
def get_backtest_result(filename: Path) -> list[BacktestHistoryEntryType]:
|
||||||
"""
|
"""
|
||||||
Get backtest result read from metadata file
|
Get backtest result read from metadata file
|
||||||
"""
|
"""
|
||||||
return _extract_backtest_result(filename)
|
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
|
||||||
"""
|
"""
|
||||||
@@ -244,12 +242,13 @@ def delete_backtest_result(file_abs: Path):
|
|||||||
"""
|
"""
|
||||||
# *.meta.json
|
# *.meta.json
|
||||||
logger.info(f"Deleting backtest result file: {file_abs.name}")
|
logger.info(f"Deleting backtest result file: {file_abs.name}")
|
||||||
file_abs_meta = file_abs.with_suffix(".meta.json")
|
|
||||||
file_abs.unlink()
|
for file in file_abs.parent.glob(f"{file_abs.stem}*"):
|
||||||
file_abs_meta.unlink()
|
logger.info(f"Deleting file: {file}")
|
||||||
|
file.unlink()
|
||||||
|
|
||||||
|
|
||||||
def update_backtest_metadata(filename: Path, strategy: str, content: Dict[str, Any]):
|
def update_backtest_metadata(filename: Path, strategy: str, content: dict[str, Any]):
|
||||||
"""
|
"""
|
||||||
Updates backtest metadata file with new content.
|
Updates backtest metadata file with new content.
|
||||||
:raises: ValueError if metadata file does not exist, or strategy is not in this file.
|
:raises: ValueError if metadata file does not exist, or strategy is not in this file.
|
||||||
@@ -275,8 +274,8 @@ def get_backtest_market_change(filename: Path, include_ts: bool = True) -> pd.Da
|
|||||||
|
|
||||||
|
|
||||||
def find_existing_backtest_stats(
|
def find_existing_backtest_stats(
|
||||||
dirname: Union[Path, str], run_ids: Dict[str, str], min_backtest_date: Optional[datetime] = None
|
dirname: Path | str, run_ids: dict[str, str], min_backtest_date: datetime | None = None
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Find existing backtest stats that match specified run IDs and load them.
|
Find existing backtest stats that match specified run IDs and load them.
|
||||||
:param dirname: pathlib.Path object, or string pointing to the file.
|
:param dirname: pathlib.Path object, or string pointing to the file.
|
||||||
@@ -287,7 +286,7 @@ def find_existing_backtest_stats(
|
|||||||
# Copy so we can modify this dict without affecting parent scope.
|
# Copy so we can modify this dict without affecting parent scope.
|
||||||
run_ids = copy(run_ids)
|
run_ids = copy(run_ids)
|
||||||
dirname = Path(dirname)
|
dirname = Path(dirname)
|
||||||
results: Dict[str, Any] = {
|
results: dict[str, Any] = {
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"strategy": {},
|
"strategy": {},
|
||||||
"strategy_comparison": [],
|
"strategy_comparison": [],
|
||||||
@@ -344,7 +343,7 @@ def _load_backtest_data_df_compatibility(df: pd.DataFrame) -> pd.DataFrame:
|
|||||||
return df
|
return df
|
||||||
|
|
||||||
|
|
||||||
def load_backtest_data(filename: Union[Path, str], strategy: Optional[str] = None) -> pd.DataFrame:
|
def load_backtest_data(filename: Path | str, strategy: str | None = None) -> pd.DataFrame:
|
||||||
"""
|
"""
|
||||||
Load backtest data file.
|
Load backtest data file.
|
||||||
:param filename: pathlib.Path object, or string pointing to a file or directory
|
:param filename: pathlib.Path object, or string pointing to a file or directory
|
||||||
@@ -401,7 +400,15 @@ def analyze_trade_parallelism(results: pd.DataFrame, timeframe: str) -> pd.DataF
|
|||||||
|
|
||||||
timeframe_freq = timeframe_to_resample_freq(timeframe)
|
timeframe_freq = timeframe_to_resample_freq(timeframe)
|
||||||
dates = [
|
dates = [
|
||||||
pd.Series(pd.date_range(row[1]["open_date"], row[1]["close_date"], freq=timeframe_freq))
|
pd.Series(
|
||||||
|
pd.date_range(
|
||||||
|
row[1]["open_date"],
|
||||||
|
row[1]["close_date"],
|
||||||
|
freq=timeframe_freq,
|
||||||
|
# Exclude right boundary - the date is the candle open date.
|
||||||
|
inclusive="left",
|
||||||
|
)
|
||||||
|
)
|
||||||
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]
|
||||||
@@ -430,7 +437,7 @@ def evaluate_result_multi(
|
|||||||
return df_final[df_final["open_trades"] > max_open_trades]
|
return df_final[df_final["open_trades"] > max_open_trades]
|
||||||
|
|
||||||
|
|
||||||
def trade_list_to_dataframe(trades: Union[List[Trade], List[LocalTrade]]) -> pd.DataFrame:
|
def trade_list_to_dataframe(trades: list[Trade] | list[LocalTrade]) -> pd.DataFrame:
|
||||||
"""
|
"""
|
||||||
Convert list of Trade objects to pandas Dataframe
|
Convert list of Trade objects to pandas Dataframe
|
||||||
:param trades: List of trade objects
|
:param trades: List of trade objects
|
||||||
@@ -444,7 +451,7 @@ def trade_list_to_dataframe(trades: Union[List[Trade], List[LocalTrade]]) -> pd.
|
|||||||
return df
|
return df
|
||||||
|
|
||||||
|
|
||||||
def load_trades_from_db(db_url: str, strategy: Optional[str] = None) -> pd.DataFrame:
|
def load_trades_from_db(db_url: str, strategy: str | None = None) -> pd.DataFrame:
|
||||||
"""
|
"""
|
||||||
Load trades from a DB (using dburl)
|
Load trades from a DB (using dburl)
|
||||||
:param db_url: Sqlite url (default format sqlite:///tradesv3.dry-run.sqlite)
|
:param db_url: Sqlite url (default format sqlite:///tradesv3.dry-run.sqlite)
|
||||||
@@ -467,7 +474,7 @@ def load_trades(
|
|||||||
db_url: str,
|
db_url: str,
|
||||||
exportfilename: Path,
|
exportfilename: Path,
|
||||||
no_trades: bool = False,
|
no_trades: bool = False,
|
||||||
strategy: Optional[str] = None,
|
strategy: str | None = None,
|
||||||
) -> pd.DataFrame:
|
) -> pd.DataFrame:
|
||||||
"""
|
"""
|
||||||
Based on configuration option 'trade_source':
|
Based on configuration option 'trade_source':
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ Functions to convert data from one format to another
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@@ -158,8 +157,8 @@ def trim_dataframe(
|
|||||||
|
|
||||||
|
|
||||||
def trim_dataframes(
|
def trim_dataframes(
|
||||||
preprocessed: Dict[str, DataFrame], timerange, startup_candles: int
|
preprocessed: dict[str, DataFrame], timerange, startup_candles: int
|
||||||
) -> Dict[str, DataFrame]:
|
) -> dict[str, DataFrame]:
|
||||||
"""
|
"""
|
||||||
Trim startup period from analyzed dataframes
|
Trim startup period from analyzed dataframes
|
||||||
:param preprocessed: Dict of pair: dataframe
|
:param preprocessed: Dict of pair: dataframe
|
||||||
@@ -167,7 +166,7 @@ def trim_dataframes(
|
|||||||
:param startup_candles: Startup-candles that should be removed
|
:param startup_candles: Startup-candles that should be removed
|
||||||
:return: Dict of trimmed dataframes
|
:return: Dict of trimmed dataframes
|
||||||
"""
|
"""
|
||||||
processed: Dict[str, DataFrame] = {}
|
processed: dict[str, DataFrame] = {}
|
||||||
|
|
||||||
for pair, df in preprocessed.items():
|
for pair, df in preprocessed.items():
|
||||||
trimed_df = trim_dataframe(df, timerange, startup_candles=startup_candles)
|
trimed_df = trim_dataframe(df, timerange, startup_candles=startup_candles)
|
||||||
|
|||||||
@@ -4,21 +4,31 @@ Functions to convert orderflow data from public_trades
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
import typing
|
|
||||||
from collections import OrderedDict
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Tuple
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
from freqtrade.constants import DEFAULT_ORDERFLOW_COLUMNS
|
from freqtrade.constants import DEFAULT_ORDERFLOW_COLUMNS, Config
|
||||||
from freqtrade.enums import RunMode
|
|
||||||
from freqtrade.exceptions import DependencyException
|
from freqtrade.exceptions import DependencyException
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
ORDERFLOW_ADDED_COLUMNS = [
|
||||||
|
"trades",
|
||||||
|
"orderflow",
|
||||||
|
"imbalances",
|
||||||
|
"stacked_imbalances_bid",
|
||||||
|
"stacked_imbalances_ask",
|
||||||
|
"max_delta",
|
||||||
|
"min_delta",
|
||||||
|
"bid",
|
||||||
|
"ask",
|
||||||
|
"delta",
|
||||||
|
"total_trades",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _init_dataframe_with_trades_columns(dataframe: pd.DataFrame):
|
def _init_dataframe_with_trades_columns(dataframe: pd.DataFrame):
|
||||||
"""
|
"""
|
||||||
@@ -26,58 +36,80 @@ def _init_dataframe_with_trades_columns(dataframe: pd.DataFrame):
|
|||||||
:param dataframe: Dataframe to populate
|
:param dataframe: Dataframe to populate
|
||||||
"""
|
"""
|
||||||
# Initialize columns with appropriate dtypes
|
# Initialize columns with appropriate dtypes
|
||||||
dataframe["trades"] = np.nan
|
for column in ORDERFLOW_ADDED_COLUMNS:
|
||||||
dataframe["orderflow"] = np.nan
|
dataframe[column] = np.nan
|
||||||
dataframe["imbalances"] = np.nan
|
|
||||||
dataframe["stacked_imbalances_bid"] = np.nan
|
|
||||||
dataframe["stacked_imbalances_ask"] = np.nan
|
|
||||||
dataframe["max_delta"] = np.nan
|
|
||||||
dataframe["min_delta"] = np.nan
|
|
||||||
dataframe["bid"] = np.nan
|
|
||||||
dataframe["ask"] = np.nan
|
|
||||||
dataframe["delta"] = np.nan
|
|
||||||
dataframe["total_trades"] = np.nan
|
|
||||||
|
|
||||||
# Ensure the 'trades' column is of object type
|
# Set columns to object type
|
||||||
dataframe["trades"] = dataframe["trades"].astype(object)
|
for column in (
|
||||||
dataframe["orderflow"] = dataframe["orderflow"].astype(object)
|
"trades",
|
||||||
dataframe["imbalances"] = dataframe["imbalances"].astype(object)
|
"orderflow",
|
||||||
dataframe["stacked_imbalances_bid"] = dataframe["stacked_imbalances_bid"].astype(object)
|
"imbalances",
|
||||||
dataframe["stacked_imbalances_ask"] = dataframe["stacked_imbalances_ask"].astype(object)
|
"stacked_imbalances_bid",
|
||||||
|
"stacked_imbalances_ask",
|
||||||
|
):
|
||||||
|
dataframe[column] = dataframe[column].astype(object)
|
||||||
|
|
||||||
|
|
||||||
|
def timeframe_to_DateOffset(timeframe: str) -> pd.DateOffset:
|
||||||
|
"""
|
||||||
|
Translates the timeframe interval value written in the human readable
|
||||||
|
form ('1m', '5m', '1h', '1d', '1w', etc.) to the number
|
||||||
|
of seconds for one timeframe interval.
|
||||||
|
"""
|
||||||
|
from freqtrade.exchange import timeframe_to_seconds
|
||||||
|
|
||||||
|
timeframe_seconds = timeframe_to_seconds(timeframe)
|
||||||
|
timeframe_minutes = timeframe_seconds // 60
|
||||||
|
if timeframe_minutes < 1:
|
||||||
|
return pd.DateOffset(seconds=timeframe_seconds)
|
||||||
|
elif 59 < timeframe_minutes < 1440:
|
||||||
|
return pd.DateOffset(hours=timeframe_minutes // 60)
|
||||||
|
elif 1440 <= timeframe_minutes < 10080:
|
||||||
|
return pd.DateOffset(days=timeframe_minutes // 1440)
|
||||||
|
elif 10000 < timeframe_minutes < 43200:
|
||||||
|
return pd.DateOffset(weeks=1)
|
||||||
|
elif timeframe_minutes >= 43200 and timeframe_minutes < 525600:
|
||||||
|
return pd.DateOffset(months=1)
|
||||||
|
elif timeframe == "1y":
|
||||||
|
return pd.DateOffset(years=1)
|
||||||
|
else:
|
||||||
|
return pd.DateOffset(minutes=timeframe_minutes)
|
||||||
|
|
||||||
|
|
||||||
def _calculate_ohlcv_candle_start_and_end(df: pd.DataFrame, timeframe: str):
|
def _calculate_ohlcv_candle_start_and_end(df: pd.DataFrame, timeframe: str):
|
||||||
from freqtrade.exchange import timeframe_to_next_date, timeframe_to_resample_freq
|
from freqtrade.exchange import timeframe_to_resample_freq
|
||||||
|
|
||||||
timeframe_frequency = timeframe_to_resample_freq(timeframe)
|
|
||||||
# calculate ohlcv candle start and end
|
|
||||||
if df is not None and not df.empty:
|
if df is not None and not df.empty:
|
||||||
|
timeframe_frequency = timeframe_to_resample_freq(timeframe)
|
||||||
|
dofs = timeframe_to_DateOffset(timeframe)
|
||||||
|
# calculate ohlcv candle start and end
|
||||||
df["datetime"] = pd.to_datetime(df["date"], unit="ms")
|
df["datetime"] = pd.to_datetime(df["date"], unit="ms")
|
||||||
df["candle_start"] = df["datetime"].dt.floor(timeframe_frequency)
|
df["candle_start"] = df["datetime"].dt.floor(timeframe_frequency)
|
||||||
# used in _now_is_time_to_refresh_trades
|
# used in _now_is_time_to_refresh_trades
|
||||||
df["candle_end"] = df["candle_start"].apply(
|
df["candle_end"] = df["candle_start"] + dofs
|
||||||
lambda candle_start: timeframe_to_next_date(timeframe, candle_start)
|
|
||||||
)
|
|
||||||
df.drop(columns=["datetime"], inplace=True)
|
df.drop(columns=["datetime"], inplace=True)
|
||||||
|
|
||||||
|
|
||||||
def populate_dataframe_with_trades(
|
def populate_dataframe_with_trades(
|
||||||
cached_grouped_trades: OrderedDict[Tuple[datetime, datetime], pd.DataFrame],
|
cached_grouped_trades: pd.DataFrame | None,
|
||||||
config,
|
config: Config,
|
||||||
dataframe: pd.DataFrame,
|
dataframe: pd.DataFrame,
|
||||||
trades: pd.DataFrame,
|
trades: pd.DataFrame,
|
||||||
) -> Tuple[pd.DataFrame, OrderedDict[Tuple[datetime, datetime], pd.DataFrame]]:
|
) -> tuple[pd.DataFrame, pd.DataFrame]:
|
||||||
"""
|
"""
|
||||||
Populates a dataframe with trades
|
Populates a dataframe with trades
|
||||||
:param dataframe: Dataframe to populate
|
:param dataframe: Dataframe to populate
|
||||||
:param trades: Trades to populate with
|
:param trades: Trades to populate with
|
||||||
:return: Dataframe with trades populated
|
:return: Dataframe with trades populated
|
||||||
"""
|
"""
|
||||||
|
|
||||||
timeframe = config["timeframe"]
|
timeframe = config["timeframe"]
|
||||||
config_orderflow = config["orderflow"]
|
config_orderflow = config["orderflow"]
|
||||||
|
|
||||||
# create columns for trades
|
# create columns for trades
|
||||||
_init_dataframe_with_trades_columns(dataframe)
|
_init_dataframe_with_trades_columns(dataframe)
|
||||||
|
if trades is None or trades.empty:
|
||||||
|
return dataframe, cached_grouped_trades
|
||||||
|
|
||||||
try:
|
try:
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
@@ -88,76 +120,57 @@ def populate_dataframe_with_trades(
|
|||||||
max_candles = config_orderflow["max_candles"]
|
max_candles = config_orderflow["max_candles"]
|
||||||
start_date = dataframe.tail(max_candles).date.iat[0]
|
start_date = dataframe.tail(max_candles).date.iat[0]
|
||||||
# slice of trades that are before current ohlcv candles to make groupby faster
|
# slice of trades that are before current ohlcv candles to make groupby faster
|
||||||
trades = trades.loc[trades.candle_start >= start_date]
|
trades = trades.loc[trades["candle_start"] >= start_date]
|
||||||
trades.reset_index(inplace=True, drop=True)
|
trades.reset_index(inplace=True, drop=True)
|
||||||
|
|
||||||
# group trades by candle start
|
# group trades by candle start
|
||||||
trades_grouped_by_candle_start = trades.groupby("candle_start", group_keys=False)
|
trades_grouped_by_candle_start = trades.groupby("candle_start", group_keys=False)
|
||||||
# Create Series to hold complex data
|
|
||||||
trades_series = pd.Series(index=dataframe.index, dtype=object)
|
|
||||||
orderflow_series = pd.Series(index=dataframe.index, dtype=object)
|
|
||||||
imbalances_series = pd.Series(index=dataframe.index, dtype=object)
|
|
||||||
stacked_imbalances_bid_series = pd.Series(index=dataframe.index, dtype=object)
|
|
||||||
stacked_imbalances_ask_series = pd.Series(index=dataframe.index, dtype=object)
|
|
||||||
|
|
||||||
trades_grouped_by_candle_start = trades.groupby("candle_start", group_keys=False)
|
candle_start: datetime
|
||||||
for candle_start, trades_grouped_df in trades_grouped_by_candle_start:
|
for candle_start, trades_grouped_df in trades_grouped_by_candle_start:
|
||||||
is_between = candle_start == dataframe["date"]
|
is_between = candle_start == dataframe["date"]
|
||||||
if is_between.any():
|
if is_between.any():
|
||||||
from freqtrade.exchange import timeframe_to_next_date
|
# there can only be one row with the same date
|
||||||
|
index = dataframe.index[is_between][0]
|
||||||
|
|
||||||
candle_next = timeframe_to_next_date(timeframe, typing.cast(datetime, candle_start))
|
if (
|
||||||
if candle_next not in trades_grouped_by_candle_start.groups:
|
cached_grouped_trades is not None
|
||||||
logger.warning(
|
and (candle_start == cached_grouped_trades["date"]).any()
|
||||||
f"candle at {candle_start} with {len(trades_grouped_df)} trades "
|
):
|
||||||
f"might be unfinished, because no finished trades at {candle_next}"
|
# Check if the trades are already in the cache
|
||||||
)
|
cache_idx = cached_grouped_trades.index[
|
||||||
|
cached_grouped_trades["date"] == candle_start
|
||||||
indices = dataframe.index[is_between].tolist()
|
][0]
|
||||||
# Add trades to each candle
|
for col in ORDERFLOW_ADDED_COLUMNS:
|
||||||
trades_series.loc[indices] = [
|
dataframe.at[index, col] = cached_grouped_trades.at[cache_idx, col]
|
||||||
trades_grouped_df.drop(columns=["candle_start", "candle_end"]).to_dict(
|
|
||||||
orient="records"
|
|
||||||
)
|
|
||||||
]
|
|
||||||
# Use caching mechanism
|
|
||||||
if (candle_start, candle_next) in cached_grouped_trades:
|
|
||||||
cache_entry = cached_grouped_trades[
|
|
||||||
(typing.cast(datetime, candle_start), candle_next)
|
|
||||||
]
|
|
||||||
# dataframe.loc[is_between] = cache_entry # doesn't take, so we need workaround:
|
|
||||||
# Create a dictionary of the column values to be assigned
|
|
||||||
update_dict = {c: cache_entry[c].iat[0] for c in cache_entry.columns}
|
|
||||||
# Assign the values using the update_dict
|
|
||||||
dataframe.loc[is_between, update_dict.keys()] = pd.DataFrame(
|
|
||||||
[update_dict], index=dataframe.loc[is_between].index
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
dataframe.at[index, "trades"] = trades_grouped_df.drop(
|
||||||
|
columns=["candle_start", "candle_end"]
|
||||||
|
).to_dict(orient="records")
|
||||||
|
|
||||||
# Calculate orderflow for each candle
|
# Calculate orderflow for each candle
|
||||||
orderflow = trades_to_volumeprofile_with_total_delta_bid_ask(
|
orderflow = trades_to_volumeprofile_with_total_delta_bid_ask(
|
||||||
trades_grouped_df, scale=config_orderflow["scale"]
|
trades_grouped_df, scale=config_orderflow["scale"]
|
||||||
)
|
)
|
||||||
orderflow_series.loc[indices] = [orderflow.to_dict(orient="index")]
|
dataframe.at[index, "orderflow"] = orderflow.to_dict(orient="index")
|
||||||
|
# orderflow_series.loc[[index]] = [orderflow.to_dict(orient="index")]
|
||||||
# Calculate imbalances for each candle's orderflow
|
# Calculate imbalances for each candle's orderflow
|
||||||
imbalances = trades_orderflow_to_imbalances(
|
imbalances = trades_orderflow_to_imbalances(
|
||||||
orderflow,
|
orderflow,
|
||||||
imbalance_ratio=config_orderflow["imbalance_ratio"],
|
imbalance_ratio=config_orderflow["imbalance_ratio"],
|
||||||
imbalance_volume=config_orderflow["imbalance_volume"],
|
imbalance_volume=config_orderflow["imbalance_volume"],
|
||||||
)
|
)
|
||||||
imbalances_series.loc[indices] = [imbalances.to_dict(orient="index")]
|
dataframe.at[index, "imbalances"] = imbalances.to_dict(orient="index")
|
||||||
|
|
||||||
stacked_imbalance_range = config_orderflow["stacked_imbalance_range"]
|
stacked_imbalance_range = config_orderflow["stacked_imbalance_range"]
|
||||||
stacked_imbalances_bid_series.loc[indices] = [
|
dataframe.at[index, "stacked_imbalances_bid"] = stacked_imbalance_bid(
|
||||||
stacked_imbalance_bid(
|
imbalances, stacked_imbalance_range=stacked_imbalance_range
|
||||||
imbalances, stacked_imbalance_range=stacked_imbalance_range
|
)
|
||||||
)
|
|
||||||
]
|
dataframe.at[index, "stacked_imbalances_ask"] = stacked_imbalance_ask(
|
||||||
stacked_imbalances_ask_series.loc[indices] = [
|
imbalances, stacked_imbalance_range=stacked_imbalance_range
|
||||||
stacked_imbalance_ask(
|
)
|
||||||
imbalances, stacked_imbalance_range=stacked_imbalance_range
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
bid = np.where(
|
bid = np.where(
|
||||||
trades_grouped_df["side"].str.contains("sell"), trades_grouped_df["amount"], 0
|
trades_grouped_df["side"].str.contains("sell"), trades_grouped_df["amount"], 0
|
||||||
@@ -167,39 +180,20 @@ def populate_dataframe_with_trades(
|
|||||||
trades_grouped_df["side"].str.contains("buy"), trades_grouped_df["amount"], 0
|
trades_grouped_df["side"].str.contains("buy"), trades_grouped_df["amount"], 0
|
||||||
)
|
)
|
||||||
deltas_per_trade = ask - bid
|
deltas_per_trade = ask - bid
|
||||||
min_delta = deltas_per_trade.cumsum().min()
|
dataframe.at[index, "max_delta"] = deltas_per_trade.cumsum().max()
|
||||||
max_delta = deltas_per_trade.cumsum().max()
|
dataframe.at[index, "min_delta"] = deltas_per_trade.cumsum().min()
|
||||||
dataframe.loc[indices, "max_delta"] = max_delta
|
|
||||||
dataframe.loc[indices, "min_delta"] = min_delta
|
|
||||||
|
|
||||||
dataframe.loc[indices, "bid"] = bid.sum()
|
dataframe.at[index, "bid"] = bid.sum()
|
||||||
dataframe.loc[indices, "ask"] = ask.sum()
|
dataframe.at[index, "ask"] = ask.sum()
|
||||||
dataframe.loc[indices, "delta"] = (
|
dataframe.at[index, "delta"] = (
|
||||||
dataframe.loc[indices, "ask"] - dataframe.loc[indices, "bid"]
|
dataframe.at[index, "ask"] - dataframe.at[index, "bid"]
|
||||||
)
|
)
|
||||||
dataframe.loc[indices, "total_trades"] = len(trades_grouped_df)
|
dataframe.at[index, "total_trades"] = len(trades_grouped_df)
|
||||||
|
|
||||||
# Cache the result
|
|
||||||
cached_grouped_trades[(typing.cast(datetime, candle_start), candle_next)] = (
|
|
||||||
dataframe.loc[is_between].copy()
|
|
||||||
)
|
|
||||||
|
|
||||||
# Maintain cache size
|
|
||||||
if (
|
|
||||||
config.get("runmode") in (RunMode.DRY_RUN, RunMode.LIVE)
|
|
||||||
and len(cached_grouped_trades) > config_orderflow["cache_size"]
|
|
||||||
):
|
|
||||||
cached_grouped_trades.popitem(last=False)
|
|
||||||
else:
|
|
||||||
logger.debug(f"Found NO candles for trades starting with {candle_start}")
|
|
||||||
logger.debug(f"trades.groups_keys in {time.time() - start_time} seconds")
|
logger.debug(f"trades.groups_keys in {time.time() - start_time} seconds")
|
||||||
|
|
||||||
# Merge the complex data Series back into the DataFrame
|
# Cache the entire dataframe
|
||||||
dataframe["trades"] = trades_series
|
cached_grouped_trades = dataframe.tail(config_orderflow["cache_size"]).copy()
|
||||||
dataframe["orderflow"] = orderflow_series
|
|
||||||
dataframe["imbalances"] = imbalances_series
|
|
||||||
dataframe["stacked_imbalances_bid"] = stacked_imbalances_bid_series
|
|
||||||
dataframe["stacked_imbalances_ask"] = stacked_imbalances_ask_series
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception("Error populating dataframe with trades")
|
logger.exception("Error populating dataframe with trades")
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ Functions to convert data from one format to another
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List
|
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from pandas import DataFrame, to_datetime
|
from pandas import DataFrame, to_datetime
|
||||||
@@ -34,7 +33,7 @@ def trades_df_remove_duplicates(trades: pd.DataFrame) -> pd.DataFrame:
|
|||||||
return trades.drop_duplicates(subset=["timestamp", "id"])
|
return trades.drop_duplicates(subset=["timestamp", "id"])
|
||||||
|
|
||||||
|
|
||||||
def trades_dict_to_list(trades: List[Dict]) -> TradeList:
|
def trades_dict_to_list(trades: list[dict]) -> TradeList:
|
||||||
"""
|
"""
|
||||||
Convert fetch_trades result into a List (to be more memory efficient).
|
Convert fetch_trades result into a List (to be more memory efficient).
|
||||||
:param trades: List of trades, as returned by ccxt.fetch_trades.
|
:param trades: List of trades, as returned by ccxt.fetch_trades.
|
||||||
@@ -91,8 +90,8 @@ def trades_to_ohlcv(trades: DataFrame, timeframe: str) -> DataFrame:
|
|||||||
|
|
||||||
|
|
||||||
def convert_trades_to_ohlcv(
|
def convert_trades_to_ohlcv(
|
||||||
pairs: List[str],
|
pairs: list[str],
|
||||||
timeframes: List[str],
|
timeframes: list[str],
|
||||||
datadir: Path,
|
datadir: Path,
|
||||||
timerange: TimeRange,
|
timerange: TimeRange,
|
||||||
erase: bool,
|
erase: bool,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Common Interface for bot and strategy to access data.
|
|||||||
import logging
|
import logging
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
from typing import Any
|
||||||
|
|
||||||
from pandas import DataFrame, Timedelta, Timestamp, to_timedelta
|
from pandas import DataFrame, Timedelta, Timestamp, to_timedelta
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ from freqtrade.data.history import get_datahandler, load_pair_history
|
|||||||
from freqtrade.enums import CandleType, RPCMessageType, RunMode, TradingMode
|
from freqtrade.enums import CandleType, RPCMessageType, RunMode, TradingMode
|
||||||
from freqtrade.exceptions import ExchangeError, OperationalException
|
from freqtrade.exceptions import ExchangeError, OperationalException
|
||||||
from freqtrade.exchange import Exchange, timeframe_to_prev_date, timeframe_to_seconds
|
from freqtrade.exchange import Exchange, timeframe_to_prev_date, timeframe_to_seconds
|
||||||
from freqtrade.exchange.types import OrderBook
|
from freqtrade.exchange.exchange_types import OrderBook
|
||||||
from freqtrade.misc import append_candles_to_dataframe
|
from freqtrade.misc import append_candles_to_dataframe
|
||||||
from freqtrade.rpc import RPCManager
|
from freqtrade.rpc import RPCManager
|
||||||
from freqtrade.rpc.rpc_types import RPCAnalyzedDFMsg
|
from freqtrade.rpc.rpc_types import RPCAnalyzedDFMsg
|
||||||
@@ -40,23 +40,23 @@ class DataProvider:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
config: Config,
|
config: Config,
|
||||||
exchange: Optional[Exchange],
|
exchange: Exchange | None,
|
||||||
pairlists=None,
|
pairlists=None,
|
||||||
rpc: Optional[RPCManager] = None,
|
rpc: RPCManager | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._config = config
|
self._config = config
|
||||||
self._exchange = exchange
|
self._exchange = exchange
|
||||||
self._pairlists = pairlists
|
self._pairlists = pairlists
|
||||||
self.__rpc = rpc
|
self.__rpc = rpc
|
||||||
self.__cached_pairs: Dict[PairWithTimeframe, Tuple[DataFrame, datetime]] = {}
|
self.__cached_pairs: dict[PairWithTimeframe, tuple[DataFrame, datetime]] = {}
|
||||||
self.__slice_index: Optional[int] = None
|
self.__slice_index: int | None = None
|
||||||
self.__slice_date: Optional[datetime] = None
|
self.__slice_date: datetime | None = None
|
||||||
|
|
||||||
self.__cached_pairs_backtesting: Dict[PairWithTimeframe, DataFrame] = {}
|
self.__cached_pairs_backtesting: dict[PairWithTimeframe, DataFrame] = {}
|
||||||
self.__producer_pairs_df: Dict[
|
self.__producer_pairs_df: dict[
|
||||||
str, Dict[PairWithTimeframe, Tuple[DataFrame, datetime]]
|
str, dict[PairWithTimeframe, tuple[DataFrame, datetime]]
|
||||||
] = {}
|
] = {}
|
||||||
self.__producer_pairs: Dict[str, List[str]] = {}
|
self.__producer_pairs: dict[str, list[str]] = {}
|
||||||
self._msg_queue: deque = deque()
|
self._msg_queue: deque = deque()
|
||||||
|
|
||||||
self._default_candle_type = self._config.get("candle_type_def", CandleType.SPOT)
|
self._default_candle_type = self._config.get("candle_type_def", CandleType.SPOT)
|
||||||
@@ -101,7 +101,7 @@ class DataProvider:
|
|||||||
self.__cached_pairs[pair_key] = (dataframe, datetime.now(timezone.utc))
|
self.__cached_pairs[pair_key] = (dataframe, datetime.now(timezone.utc))
|
||||||
|
|
||||||
# For multiple producers we will want to merge the pairlists instead of overwriting
|
# For multiple producers we will want to merge the pairlists instead of overwriting
|
||||||
def _set_producer_pairs(self, pairlist: List[str], producer_name: str = "default"):
|
def _set_producer_pairs(self, pairlist: list[str], producer_name: str = "default"):
|
||||||
"""
|
"""
|
||||||
Set the pairs received to later be used.
|
Set the pairs received to later be used.
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ class DataProvider:
|
|||||||
"""
|
"""
|
||||||
self.__producer_pairs[producer_name] = pairlist
|
self.__producer_pairs[producer_name] = pairlist
|
||||||
|
|
||||||
def get_producer_pairs(self, producer_name: str = "default") -> List[str]:
|
def get_producer_pairs(self, producer_name: str = "default") -> list[str]:
|
||||||
"""
|
"""
|
||||||
Get the pairs cached from the producer
|
Get the pairs cached from the producer
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ class DataProvider:
|
|||||||
timeframe: str,
|
timeframe: str,
|
||||||
candle_type: CandleType,
|
candle_type: CandleType,
|
||||||
producer_name: str = "default",
|
producer_name: str = "default",
|
||||||
) -> Tuple[bool, int]:
|
) -> tuple[bool, int]:
|
||||||
"""
|
"""
|
||||||
Append a candle to the existing external dataframe. The incoming dataframe
|
Append a candle to the existing external dataframe. The incoming dataframe
|
||||||
must have at least 1 candle.
|
must have at least 1 candle.
|
||||||
@@ -255,10 +255,10 @@ class DataProvider:
|
|||||||
def get_producer_df(
|
def get_producer_df(
|
||||||
self,
|
self,
|
||||||
pair: str,
|
pair: str,
|
||||||
timeframe: Optional[str] = None,
|
timeframe: str | None = None,
|
||||||
candle_type: Optional[CandleType] = None,
|
candle_type: CandleType | None = None,
|
||||||
producer_name: str = "default",
|
producer_name: str = "default",
|
||||||
) -> Tuple[DataFrame, datetime]:
|
) -> tuple[DataFrame, datetime]:
|
||||||
"""
|
"""
|
||||||
Get the pair data from producers.
|
Get the pair data from producers.
|
||||||
|
|
||||||
@@ -349,7 +349,7 @@ class DataProvider:
|
|||||||
return total_candles
|
return total_candles
|
||||||
|
|
||||||
def get_pair_dataframe(
|
def get_pair_dataframe(
|
||||||
self, pair: str, timeframe: Optional[str] = None, candle_type: str = ""
|
self, pair: str, timeframe: str | None = None, candle_type: str = ""
|
||||||
) -> DataFrame:
|
) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Return pair candle (OHLCV) data, either live or cached historical -- depending
|
Return pair candle (OHLCV) data, either live or cached historical -- depending
|
||||||
@@ -377,7 +377,7 @@ class DataProvider:
|
|||||||
logger.warning(f"No data found for ({pair}, {timeframe}, {candle_type}).")
|
logger.warning(f"No data found for ({pair}, {timeframe}, {candle_type}).")
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_analyzed_dataframe(self, pair: str, timeframe: str) -> Tuple[DataFrame, datetime]:
|
def get_analyzed_dataframe(self, pair: str, timeframe: str) -> tuple[DataFrame, datetime]:
|
||||||
"""
|
"""
|
||||||
Retrieve the analyzed dataframe. Returns the full dataframe in trade mode (live / dry),
|
Retrieve the analyzed dataframe. Returns the full dataframe in trade mode (live / dry),
|
||||||
and the last 1000 candles (up to the time evaluated at this moment) in all other modes.
|
and the last 1000 candles (up to the time evaluated at this moment) in all other modes.
|
||||||
@@ -408,7 +408,7 @@ class DataProvider:
|
|||||||
"""
|
"""
|
||||||
return RunMode(self._config.get("runmode", RunMode.OTHER))
|
return RunMode(self._config.get("runmode", RunMode.OTHER))
|
||||||
|
|
||||||
def current_whitelist(self) -> List[str]:
|
def current_whitelist(self) -> list[str]:
|
||||||
"""
|
"""
|
||||||
fetch latest available whitelist.
|
fetch latest available whitelist.
|
||||||
|
|
||||||
@@ -437,7 +437,7 @@ class DataProvider:
|
|||||||
def refresh(
|
def refresh(
|
||||||
self,
|
self,
|
||||||
pairlist: ListPairsWithTimeframes,
|
pairlist: ListPairsWithTimeframes,
|
||||||
helping_pairs: Optional[ListPairsWithTimeframes] = None,
|
helping_pairs: ListPairsWithTimeframes | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Refresh data, called with each cycle
|
Refresh data, called with each cycle
|
||||||
@@ -471,7 +471,7 @@ class DataProvider:
|
|||||||
return list(self._exchange._klines.keys())
|
return list(self._exchange._klines.keys())
|
||||||
|
|
||||||
def ohlcv(
|
def ohlcv(
|
||||||
self, pair: str, timeframe: Optional[str] = None, copy: bool = True, candle_type: str = ""
|
self, pair: str, timeframe: str | None = None, copy: bool = True, candle_type: str = ""
|
||||||
) -> DataFrame:
|
) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Get candle (OHLCV) data for the given pair as DataFrame
|
Get candle (OHLCV) data for the given pair as DataFrame
|
||||||
@@ -497,7 +497,7 @@ class DataProvider:
|
|||||||
return DataFrame()
|
return DataFrame()
|
||||||
|
|
||||||
def trades(
|
def trades(
|
||||||
self, pair: str, timeframe: Optional[str] = None, copy: bool = True, candle_type: str = ""
|
self, pair: str, timeframe: str | None = None, copy: bool = True, candle_type: str = ""
|
||||||
) -> DataFrame:
|
) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Get candle (TRADES) data for the given pair as DataFrame
|
Get candle (TRADES) data for the given pair as DataFrame
|
||||||
@@ -520,22 +520,16 @@ class DataProvider:
|
|||||||
return self._exchange.trades(
|
return self._exchange.trades(
|
||||||
(pair, timeframe or self._config["timeframe"], _candle_type), copy=copy
|
(pair, timeframe or self._config["timeframe"], _candle_type), copy=copy
|
||||||
)
|
)
|
||||||
elif self.runmode in (RunMode.BACKTEST, RunMode.HYPEROPT):
|
else:
|
||||||
_candle_type = (
|
|
||||||
CandleType.from_string(candle_type)
|
|
||||||
if candle_type != ""
|
|
||||||
else self._config["candle_type_def"]
|
|
||||||
)
|
|
||||||
data_handler = get_datahandler(
|
data_handler = get_datahandler(
|
||||||
self._config["datadir"], data_format=self._config["dataformat_trades"]
|
self._config["datadir"], data_format=self._config["dataformat_trades"]
|
||||||
)
|
)
|
||||||
trades_df = data_handler.trades_load(pair, TradingMode.FUTURES)
|
trades_df = data_handler.trades_load(
|
||||||
|
pair, self._config.get("trading_mode", TradingMode.SPOT)
|
||||||
|
)
|
||||||
return trades_df
|
return trades_df
|
||||||
|
|
||||||
else:
|
def market(self, pair: str) -> dict[str, Any] | None:
|
||||||
return DataFrame()
|
|
||||||
|
|
||||||
def market(self, pair: str) -> Optional[Dict[str, Any]]:
|
|
||||||
"""
|
"""
|
||||||
Return market data for the pair
|
Return market data for the pair
|
||||||
:param pair: Pair to get the data for
|
:param pair: Pair to get the data for
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user