Compare commits
951 Commits
inverse-fu
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e0d21dbc3 | ||
|
|
1a3f76a38e | ||
|
|
6e5daa90ff | ||
|
|
0f2b07a553 | ||
|
|
b2ebd2d5c5 | ||
|
|
a704fbe13e | ||
|
|
f95420082b | ||
|
|
4ff679e987 | ||
|
|
04f1f01f3b | ||
|
|
7d39502faf | ||
|
|
bd1e644e06 | ||
|
|
ccb6d641f1 | ||
|
|
515e069d6c | ||
|
|
c2503e4e8e | ||
|
|
9d95c82982 | ||
|
|
94460749cc | ||
|
|
493e5d9e41 | ||
|
|
18da8c87b7 | ||
|
|
8da9b0818e | ||
|
|
91ffb61d2d | ||
|
|
cfe28d677d | ||
|
|
fe74aadc15 | ||
|
|
46e4ef5557 | ||
|
|
2a4de527d3 | ||
|
|
2d67f5f46f | ||
|
|
074b91f633 | ||
|
|
cff9e69446 | ||
|
|
e3b5cef2c4 | ||
|
|
43f260d564 | ||
|
|
4f8f08f04d | ||
|
|
7201e9c483 | ||
|
|
b2a9191b30 | ||
|
|
deae30482c | ||
|
|
f9592183cd | ||
|
|
2f9a9235db | ||
|
|
5d89a48193 | ||
|
|
cd7c41a2e0 | ||
|
|
b75f1e87b5 | ||
|
|
443c2c58fb | ||
|
|
accbec8d2b | ||
|
|
d0cec7f964 | ||
|
|
b9472b86b5 | ||
|
|
1d894d40e7 | ||
|
|
da16d68fd3 | ||
|
|
42c8028ba9 | ||
|
|
6299fdca96 | ||
|
|
d7cf800122 | ||
|
|
2e3b74b497 | ||
|
|
8a3d1744da | ||
|
|
9b3354ea4b | ||
|
|
0b339ce99e | ||
|
|
659e31fc6e | ||
|
|
33068dd814 | ||
|
|
1d39872116 | ||
|
|
dabd624d8e | ||
|
|
f858447bd7 | ||
|
|
4e3f3fd679 | ||
|
|
7a78c606a7 | ||
|
|
75c0416dc9 | ||
|
|
01ddba050a | ||
|
|
d57d8226af | ||
|
|
892bb78a7a | ||
|
|
683afc24e7 | ||
|
|
55270a232d | ||
|
|
8148a7d88b | ||
|
|
e96aa74112 | ||
|
|
9470d9758a | ||
|
|
02830c5b1d | ||
|
|
4deaa03466 | ||
|
|
8931d7ab0c | ||
|
|
1300b0983d | ||
|
|
26d8ddaa9d | ||
|
|
6e1fb66bf8 | ||
|
|
4b30a0daee | ||
|
|
b707a145d2 | ||
|
|
d9f383cb74 | ||
|
|
cf24917508 | ||
|
|
fb40bbd46e | ||
|
|
b3a67bb0bf | ||
|
|
c1c8eef2f2 | ||
|
|
2b5a0c5a61 | ||
|
|
646de628fd | ||
|
|
5040104114 | ||
|
|
47783b1f70 | ||
|
|
6f45550a26 | ||
|
|
57199568e9 | ||
|
|
cdb222b203 | ||
|
|
9fede6adae | ||
|
|
8e3a255926 | ||
|
|
697cf51827 | ||
|
|
2875954713 | ||
|
|
905f53c884 | ||
|
|
95ca0d5c96 | ||
|
|
57032a5069 | ||
|
|
96c11a6b12 | ||
|
|
f782a40de3 | ||
|
|
4bb7c3e1cd | ||
|
|
dd9ecfb312 | ||
|
|
be065c56c3 | ||
|
|
611023fec7 | ||
|
|
edae58368b | ||
|
|
34a4cf456d | ||
|
|
fc25d48ccc | ||
|
|
b8a1f131f3 | ||
|
|
704acb01b2 | ||
|
|
cb2a01424a | ||
|
|
91b6875ed7 | ||
|
|
30d4eb08e2 | ||
|
|
8629922e0b | ||
|
|
832e1f5d00 | ||
|
|
50e62ee53c | ||
|
|
a0d9a7bfae | ||
|
|
4198892e91 | ||
|
|
29f015a46a | ||
|
|
c8bf332636 | ||
|
|
65c3c2c621 | ||
|
|
ed8ccc2a5d | ||
|
|
6a6dcecd84 | ||
|
|
a88aa0f1bd | ||
|
|
a6cdf0cdfe | ||
|
|
bc78dbe014 | ||
|
|
2194271eaf | ||
|
|
481f5730bc | ||
|
|
0f3d6015a5 | ||
|
|
d7abeec624 | ||
|
|
fc8c76fa06 | ||
|
|
fcfbbfdf36 | ||
|
|
bf9a55ea16 | ||
|
|
f99682c153 | ||
|
|
dfcadd9c7c | ||
|
|
93d6f4040e | ||
|
|
55dbf55cfe | ||
|
|
ce9b2bcb70 | ||
|
|
bf5f75149b | ||
|
|
037e3784f6 | ||
|
|
47ac3015a6 | ||
|
|
b0bebba533 | ||
|
|
ec3f30f649 | ||
|
|
efab55f69f | ||
|
|
7c1fb25539 | ||
|
|
cfee29d71b | ||
|
|
add50137e4 | ||
|
|
a4607e6532 | ||
|
|
9800e0cf80 | ||
|
|
044b7cae26 | ||
|
|
0cbf910f6a | ||
|
|
44b88fb555 | ||
|
|
f42f2b482d | ||
|
|
8d977276c2 | ||
|
|
1dbec2f2a8 | ||
|
|
fb912b8580 | ||
|
|
846707fd4a | ||
|
|
895f3e76c3 | ||
|
|
7164d84f93 | ||
|
|
ffdf6e21bb | ||
|
|
3cdfb8d730 | ||
|
|
aef34d77c1 | ||
|
|
bfec5ffcfb | ||
|
|
f639a46a78 | ||
|
|
cea30d918e | ||
|
|
075a97c8e2 | ||
|
|
4580d51ff3 | ||
|
|
4182b98493 | ||
|
|
bb3b54e2af | ||
|
|
befc52c4d3 | ||
|
|
53b7e4779f | ||
|
|
09fac79497 | ||
|
|
af90ae84ed | ||
|
|
61300d6d41 | ||
|
|
ce10251ca4 | ||
|
|
b11c573d0c | ||
|
|
e08431d215 | ||
|
|
3b50419d8d | ||
|
|
5b977de2e1 | ||
|
|
e040c40f6f | ||
|
|
6b8bc65263 | ||
|
|
7400730c89 | ||
|
|
227b166764 | ||
|
|
800d71ff48 | ||
|
|
61885e85e8 | ||
|
|
0f9f3cd561 | ||
|
|
0acff13cbd | ||
|
|
410a68c567 | ||
|
|
c636102096 | ||
|
|
6c31456727 | ||
|
|
83c8a3c32f | ||
|
|
a191135052 | ||
|
|
4d9bb206fb | ||
|
|
0eb67ed090 | ||
|
|
79bad9dd17 | ||
|
|
c0a6f0d182 | ||
|
|
a6f992d726 | ||
|
|
a9bc53b516 | ||
|
|
f5150cbeca | ||
|
|
cd2967ad63 | ||
|
|
882430b3be | ||
|
|
a737ffc9c8 | ||
|
|
4278da0932 | ||
|
|
0c322291e5 | ||
|
|
bda7762eba | ||
|
|
db117cb7b9 | ||
|
|
de5cb75e09 | ||
|
|
5747b71a8f | ||
|
|
39dd97d8bd | ||
|
|
3f93c01eef | ||
|
|
504ca51f58 | ||
|
|
58c64c57df | ||
|
|
1c5d77d5d6 | ||
|
|
9af852503d | ||
|
|
cc8595c782 | ||
|
|
553aa93594 | ||
|
|
6b47213fa6 | ||
|
|
fdf426844c | ||
|
|
b3e243ea32 | ||
|
|
f39bcf6976 | ||
|
|
d8813bfade | ||
|
|
0552443541 | ||
|
|
c47d2832fb | ||
|
|
101a0049cd | ||
|
|
c364a2e65e | ||
|
|
9a50a4bb0b | ||
|
|
ed3647359d | ||
|
|
b3afd401e7 | ||
|
|
0f64154319 | ||
|
|
d4d30f590d | ||
|
|
7ea302ca41 | ||
|
|
a4270c2ee4 | ||
|
|
5bd98601e1 | ||
|
|
60f51b91be | ||
|
|
bb884c2904 | ||
|
|
824198188a | ||
|
|
80f67edd6c | ||
|
|
c753832e49 | ||
|
|
b4849f6608 | ||
|
|
c112965882 | ||
|
|
fa08f6674a | ||
|
|
ca7ae8d7d6 | ||
|
|
c404805c8b | ||
|
|
695cec2311 | ||
|
|
cf7e5845ac | ||
|
|
a2aafe2918 | ||
|
|
ea24a3b307 | ||
|
|
2e6167e956 | ||
|
|
7d81da68a7 | ||
|
|
99d371e508 | ||
|
|
cacfe08ea4 | ||
|
|
d343414ede | ||
|
|
2a966ff9c0 | ||
|
|
cc8d496bf8 | ||
|
|
b3f5a2cd8e | ||
|
|
989f2b0d72 | ||
|
|
19e944fa32 | ||
|
|
eb38fd8ac5 | ||
|
|
664f32a133 | ||
|
|
7ac2c1e758 | ||
|
|
44f4002ec6 | ||
|
|
4b254d7a13 | ||
|
|
83dec4e2d6 | ||
|
|
4a3e7c4c77 | ||
|
|
18e26f3f8e | ||
|
|
07c8fa463f | ||
|
|
5d7459631d | ||
|
|
7b392bdf57 | ||
|
|
bfab35d51d | ||
|
|
e4de8e5a18 | ||
|
|
ffbfe03e60 | ||
|
|
d6928c2f1a | ||
|
|
7befe7b0e3 | ||
|
|
e4a15ae594 | ||
|
|
77123dbfb6 | ||
|
|
f2db32dcac | ||
|
|
ae9001afb9 | ||
|
|
add0e5c798 | ||
|
|
1c8ee6e87d | ||
|
|
43156453bc | ||
|
|
fa76f753e8 | ||
|
|
9aa63a5fe6 | ||
|
|
a259707594 | ||
|
|
21021075d6 | ||
|
|
fc586ae475 | ||
|
|
bb6f42af11 | ||
|
|
ec6eda3229 | ||
|
|
966928bc67 | ||
|
|
9cfa28f262 | ||
|
|
70aa3525ba | ||
|
|
dcf83bd28b | ||
|
|
be7ee9afb5 | ||
|
|
fd4f2b9347 | ||
|
|
e0f76db00d | ||
|
|
6c16b4b9b0 | ||
|
|
beea05cd15 | ||
|
|
392cb82287 | ||
|
|
dfc9bd59bc | ||
|
|
57e60a9042 | ||
|
|
9d48160070 | ||
|
|
bfb5fcf2f8 | ||
|
|
3c1980a5bb | ||
|
|
abffaa5492 | ||
|
|
6452a29727 | ||
|
|
e70a764655 | ||
|
|
3b90ee48e0 | ||
|
|
860527f9a8 | ||
|
|
dab5b47426 | ||
|
|
0a4f443b29 | ||
|
|
86e7e72fe3 | ||
|
|
faced4ba56 | ||
|
|
ba244d3b01 | ||
|
|
1584470345 | ||
|
|
e63afc9211 | ||
|
|
a2ff8bf355 | ||
|
|
73b4ebccc4 | ||
|
|
80f1296e8c | ||
|
|
4fde6819cd | ||
|
|
821205c60e | ||
|
|
4f0cc3b112 | ||
|
|
3817c97df1 | ||
|
|
59ea893ce2 | ||
|
|
18f7844368 | ||
|
|
6e78080ca2 | ||
|
|
a084b27ed3 | ||
|
|
01ad214965 | ||
|
|
9f1c0e32d4 | ||
|
|
2c14305de7 | ||
|
|
5e3842d0fb | ||
|
|
511a8933f9 | ||
|
|
289c0710f2 | ||
|
|
b7eea30bbd | ||
|
|
6943739043 | ||
|
|
1cf932d5f7 | ||
|
|
b16f74d6a8 | ||
|
|
1d6f0b1eeb | ||
|
|
19c22d51aa | ||
|
|
a893838cef | ||
|
|
455442260a | ||
|
|
12a962debf | ||
|
|
d6132e0dd8 | ||
|
|
bac2f1ca12 | ||
|
|
c40d029d22 | ||
|
|
5c63f19f5d | ||
|
|
bd7982cb9d | ||
|
|
f7c7b64b44 | ||
|
|
941630fcf5 | ||
|
|
55eb7835b8 | ||
|
|
28561472da | ||
|
|
2dc59f6cab | ||
|
|
f78f352cb6 | ||
|
|
d922f9875d | ||
|
|
9e316d8e67 | ||
|
|
883a598db4 | ||
|
|
53bfbd4025 | ||
|
|
06d4dcbee2 | ||
|
|
54dfb92c3a | ||
|
|
8b21948f57 | ||
|
|
aabbb2c433 | ||
|
|
3ec8852e81 | ||
|
|
b197ce3c38 | ||
|
|
e121c24d38 | ||
|
|
bb30875850 | ||
|
|
50f88a4d47 | ||
|
|
8704051d43 | ||
|
|
0977d8144c | ||
|
|
412b7d4b8d | ||
|
|
50f68cf898 | ||
|
|
d5265aa627 | ||
|
|
13ef3fb99a | ||
|
|
d81cc70422 | ||
|
|
22ab5a341d | ||
|
|
f79c79bdef | ||
|
|
7d5bdc04f0 | ||
|
|
5c70f0e124 | ||
|
|
6ee96a3646 | ||
|
|
551cd9321c | ||
|
|
2657ff8fe7 | ||
|
|
1ca15c00f2 | ||
|
|
c85918bc04 | ||
|
|
b0c3878da5 | ||
|
|
4686438fc2 | ||
|
|
b1c8581e88 | ||
|
|
9ee2a7508e | ||
|
|
963c886738 | ||
|
|
2b77067ce5 | ||
|
|
2a7f4d2fc3 | ||
|
|
35e0353139 | ||
|
|
a783576960 | ||
|
|
77dd120d36 | ||
|
|
68f3cc7636 | ||
|
|
1b6b026c27 | ||
|
|
e0edba6f1d | ||
|
|
0501e64824 | ||
|
|
f40a9bea66 | ||
|
|
4987b6d3c7 | ||
|
|
878e46bf1d | ||
|
|
d67e7ca10a | ||
|
|
3a2e54ca19 | ||
|
|
a03f255a71 | ||
|
|
33d954b78c | ||
|
|
bd56604a9f | ||
|
|
c1f08c837a | ||
|
|
b738629f28 | ||
|
|
58ecbaaaba | ||
|
|
cbd510b4c8 | ||
|
|
12ab5932ad | ||
|
|
f8e3194fe9 | ||
|
|
e7b7d28b91 | ||
|
|
7130a38ab5 | ||
|
|
704d5a9e48 | ||
|
|
183557e4b1 | ||
|
|
4584f13934 | ||
|
|
f26eff4fdc | ||
|
|
d960df58af | ||
|
|
7cb101649f | ||
|
|
c9ec5ec7ed | ||
|
|
9dff005c72 | ||
|
|
2b1573a952 | ||
|
|
7f05aca403 | ||
|
|
d1b4f4ee36 | ||
|
|
60e5c8302c | ||
|
|
36add74834 | ||
|
|
4d41cecb48 | ||
|
|
f12e29f6da | ||
|
|
50d0e96a7e | ||
|
|
f4ee4b444c | ||
|
|
1c539e4473 | ||
|
|
6745453b69 | ||
|
|
c218d69f16 | ||
|
|
169f3c85c4 | ||
|
|
ccbc8697ff | ||
|
|
629be96aa4 | ||
|
|
265ec07e9c | ||
|
|
e66cc22e05 | ||
|
|
b20716f1dc | ||
|
|
74c2a7e17f | ||
|
|
f1a0f5992c | ||
|
|
8d59993999 | ||
|
|
640fdd5132 | ||
|
|
a8d00455b2 | ||
|
|
18c8960fdb | ||
|
|
3be52a20e7 | ||
|
|
5a83331bc0 | ||
|
|
2c5e48ac52 | ||
|
|
09a16501ee | ||
|
|
448face575 | ||
|
|
394d4bd294 | ||
|
|
713b7cc7a1 | ||
|
|
9ce26a3ef7 | ||
|
|
e9dba8d039 | ||
|
|
8cbe3badc0 | ||
|
|
a718d1f03e | ||
|
|
865a7c578a | ||
|
|
2f72a1a285 | ||
|
|
f0fb5dc53a | ||
|
|
acac321259 | ||
|
|
14b1b9441f | ||
|
|
35b492a6b9 | ||
|
|
d27cfea71e | ||
|
|
7b0d81c237 | ||
|
|
7bb6020efb | ||
|
|
45594c6902 | ||
|
|
527952a74b | ||
|
|
b3e29d4703 | ||
|
|
78bcba38c1 | ||
|
|
a2e20b6f91 | ||
|
|
3bf11b37bf | ||
|
|
4fffc22306 | ||
|
|
2a59f618f9 | ||
|
|
ade7faa5ad | ||
|
|
aaca7793e1 | ||
|
|
8fd612178f | ||
|
|
bea0be323a | ||
|
|
3fc5e2d3fe | ||
|
|
bf43a12cd6 | ||
|
|
50b492f155 | ||
|
|
2b9afaabbe | ||
|
|
ddb8252196 | ||
|
|
44d2247515 | ||
|
|
f3aaae3b97 | ||
|
|
67e954dd2d | ||
|
|
a059e0adfa | ||
|
|
e04c02b3b5 | ||
|
|
1314392d11 | ||
|
|
ab7bb373a0 | ||
|
|
bbca8ea6bd | ||
|
|
2e2d9b03d4 | ||
|
|
dc101a1c82 | ||
|
|
2e36e97d3a | ||
|
|
3029588e75 | ||
|
|
94914ad2ce | ||
|
|
f89b34d7e9 | ||
|
|
47eecfab32 | ||
|
|
aab03cb9d9 | ||
|
|
3b728a8317 | ||
|
|
a825e2ceb5 | ||
|
|
b623aa9e36 | ||
|
|
afcdc2e018 | ||
|
|
aae6345813 | ||
|
|
0e62855aa5 | ||
|
|
5ae4039017 | ||
|
|
500e2797f8 | ||
|
|
7d14a3c152 | ||
|
|
ea6557ebd3 | ||
|
|
30f3aeddef | ||
|
|
5625d97fa0 | ||
|
|
01eb8c46c4 | ||
|
|
b9b7b96392 | ||
|
|
11847688e9 | ||
|
|
aa9ce33862 | ||
|
|
3f7ac6e7c4 | ||
|
|
1a8dfca8f7 | ||
|
|
470447ddc7 | ||
|
|
c3f1f7265a | ||
|
|
5fb801df79 | ||
|
|
e4bcae7bef | ||
|
|
65f60b8c1f | ||
|
|
4f7d3508c2 | ||
|
|
2928b83e16 | ||
|
|
104744827c | ||
|
|
b37f7af295 | ||
|
|
455c3b6b56 | ||
|
|
6d294a6750 | ||
|
|
5a3b9aa44b | ||
|
|
2b05a1feac | ||
|
|
7aa978fc60 | ||
|
|
d84a5453fd | ||
|
|
423ef882b8 | ||
|
|
97758b6248 | ||
|
|
a192e7c39f | ||
|
|
43857b4401 | ||
|
|
256e7d5123 | ||
|
|
ecbffe6a46 | ||
|
|
be585cea44 | ||
|
|
f01ef633b0 | ||
|
|
72a4d71847 | ||
|
|
0ef7b43cf9 | ||
|
|
c1cca2a0b3 | ||
|
|
1c3cb990e4 | ||
|
|
ece99024ef | ||
|
|
8ce260b267 | ||
|
|
ad72d43bd9 | ||
|
|
fbe41c210e | ||
|
|
ed7343e3c0 | ||
|
|
178e111087 | ||
|
|
d810d38207 | ||
|
|
c0a31b7cdc | ||
|
|
3136802e25 | ||
|
|
a08e40fbe3 | ||
|
|
c2cdd75668 | ||
|
|
8a8078bf09 | ||
|
|
d1c0d05281 | ||
|
|
1b0c95aab7 | ||
|
|
f5cc955bd4 | ||
|
|
06ca849cdf | ||
|
|
b1d76d5183 | ||
|
|
8aea5223a0 | ||
|
|
d9a637a481 | ||
|
|
eaf4308ed0 | ||
|
|
29d421e31a | ||
|
|
25b4351b95 | ||
|
|
93dc310408 | ||
|
|
80cce88daa | ||
|
|
7f5606d06d | ||
|
|
dc95b24b61 | ||
|
|
24dc180a51 | ||
|
|
4be7ce1d61 | ||
|
|
bd66d4f93b | ||
|
|
5b54e7abd2 | ||
|
|
8a2b627558 | ||
|
|
7725463e60 | ||
|
|
3d678d2395 | ||
|
|
e433cbe72b | ||
|
|
e3278af3ce | ||
|
|
780a1e8e74 | ||
|
|
e16a4b5708 | ||
|
|
4756529fa4 | ||
|
|
7b68444e9b | ||
|
|
cb35b43d2f | ||
|
|
95dc42af81 | ||
|
|
fa5d1d6a7c | ||
|
|
b492093aa2 | ||
|
|
9faf55f398 | ||
|
|
38f88eb825 | ||
|
|
45ec37ceb4 | ||
|
|
c63fd82096 | ||
|
|
49085fbc12 | ||
|
|
5e91aab844 | ||
|
|
db8e317f77 | ||
|
|
9890569743 | ||
|
|
2ecf98e428 | ||
|
|
54f5a31f5d | ||
|
|
8786ce326c | ||
|
|
cff0698a65 | ||
|
|
6e5cc29177 | ||
|
|
53297462d4 | ||
|
|
f193d33afa | ||
|
|
0277b40c9c | ||
|
|
748447ce8a | ||
|
|
36a849b550 | ||
|
|
7f6e18ded4 | ||
|
|
c43f3c69ea | ||
|
|
89ac1ce260 | ||
|
|
d1a2738cf4 | ||
|
|
561ba38ec3 | ||
|
|
47fa228094 | ||
|
|
ccd0a6df55 | ||
|
|
35a3f6ac71 | ||
|
|
a4d3745b6d | ||
|
|
7e6fced118 | ||
|
|
a671f70d45 | ||
|
|
22f01c639b | ||
|
|
65274f7dc0 | ||
|
|
7301e0e0a7 | ||
|
|
b727b8a86e | ||
|
|
c8a3ccc1bf | ||
|
|
5fae57fdc9 | ||
|
|
eb6809f8a2 | ||
|
|
5ac7eeb9c5 | ||
|
|
709419c337 | ||
|
|
aceb9764cd | ||
|
|
a661f20a63 | ||
|
|
71ea22916c | ||
|
|
0c081b6f1a | ||
|
|
ad353b45d1 | ||
|
|
33a6ea1d81 | ||
|
|
bda6338632 | ||
|
|
da963398c0 | ||
|
|
d7ce8c59ce | ||
|
|
6897cfdfff | ||
|
|
b6728bee2e | ||
|
|
45cf1eb8fe | ||
|
|
1e5238f002 | ||
|
|
cf4d852088 | ||
|
|
cbd54f3179 | ||
|
|
18854bb103 | ||
|
|
8e502d070c | ||
|
|
65031d809b | ||
|
|
59fef71cdf | ||
|
|
167fa738ff | ||
|
|
da82aecacd | ||
|
|
9e77f8807d | ||
|
|
5f342de4af | ||
|
|
2f24de176d | ||
|
|
2ad1caf06d | ||
|
|
fb566531a4 | ||
|
|
3255ad65fb | ||
|
|
4266b796cb | ||
|
|
45c27de54a | ||
|
|
be1a485252 | ||
|
|
29c1a9932a | ||
|
|
392bc6d933 | ||
|
|
a06263716d | ||
|
|
39028eb94c | ||
|
|
a478742634 | ||
|
|
72afeb9550 | ||
|
|
a03e4873a8 | ||
|
|
4698e58469 | ||
|
|
188d39fa3a | ||
|
|
a13db145ea | ||
|
|
e0b81de17d | ||
|
|
769daa62a2 | ||
|
|
82d15e0e33 | ||
|
|
c6953a5118 | ||
|
|
a89fe6333f | ||
|
|
425808703a | ||
|
|
64d280f012 | ||
|
|
6024d2d813 | ||
|
|
ceafe9a529 | ||
|
|
a8d6dce3f4 | ||
|
|
d95a0ccbee | ||
|
|
2df8487351 | ||
|
|
5ca5a6b464 | ||
|
|
42396f0771 | ||
|
|
b2bee0a357 | ||
|
|
0d3ab626ec | ||
|
|
ec77f93c9b | ||
|
|
accbb69484 | ||
|
|
62f21a0e43 | ||
|
|
1e08c8e219 | ||
|
|
8d619b8eda | ||
|
|
b33bd577f6 | ||
|
|
d63adaeef2 | ||
|
|
816ea0ca02 | ||
|
|
27a9985559 | ||
|
|
9201f5208a | ||
|
|
1719833102 | ||
|
|
c3e4808389 | ||
|
|
3d0005360b | ||
|
|
7d0cbd9cb0 | ||
|
|
37a7517c10 | ||
|
|
6de6cc24a7 | ||
|
|
9ce70f3a67 | ||
|
|
2f7f177244 | ||
|
|
73b5ff02fb | ||
|
|
b4161fcd6a | ||
|
|
5fa5dc4bc8 | ||
|
|
f620e71f6c | ||
|
|
f4e98ad89e | ||
|
|
ff3d8aec8b | ||
|
|
36c5a44dc5 | ||
|
|
b7e5ac1ebd | ||
|
|
979569f53d | ||
|
|
a2d7e720b8 | ||
|
|
9740179625 | ||
|
|
c4c0c3dbab | ||
|
|
3177cc06e2 | ||
|
|
1205d5cae5 | ||
|
|
83ff60a191 | ||
|
|
fbf08dfee8 | ||
|
|
412564f88f | ||
|
|
ee117e88a0 | ||
|
|
addbe2bf2e | ||
|
|
f564786c82 | ||
|
|
dc00eca895 | ||
|
|
9baf1ed456 | ||
|
|
0dfc45c493 | ||
|
|
7a113c8bfb | ||
|
|
361a6d5368 | ||
|
|
7313f1d92e | ||
|
|
d8be22db1c | ||
|
|
7e86dd4326 | ||
|
|
c06894e776 | ||
|
|
023ae30efb | ||
|
|
7e87c66b14 | ||
|
|
34cbd48695 | ||
|
|
8970b04c4d | ||
|
|
19f66a67f5 | ||
|
|
0181234352 | ||
|
|
bc848d695a | ||
|
|
76b7f35e7d | ||
|
|
b259be3571 | ||
|
|
a85da9dddb | ||
|
|
956f143373 | ||
|
|
d011733b47 | ||
|
|
825ab8cfe8 | ||
|
|
4098460336 | ||
|
|
bc8ef7697d | ||
|
|
94f23a37d0 | ||
|
|
b493ffbce5 | ||
|
|
31c510762d | ||
|
|
0c85d0d19b | ||
|
|
bba46bb55a | ||
|
|
1e3c88bb86 | ||
|
|
db71c8d5a3 | ||
|
|
94f9a45527 | ||
|
|
5a4d47942d | ||
|
|
02c455a530 | ||
|
|
95b8ca282f | ||
|
|
5d3c17df72 | ||
|
|
7fa4617b23 | ||
|
|
80c3c1a270 | ||
|
|
6cf090b8a4 | ||
|
|
dcce2e9b96 | ||
|
|
0dd0a89063 | ||
|
|
f65763be6d | ||
|
|
31eed9c650 | ||
|
|
e5f4e916f8 | ||
|
|
9aacad5773 | ||
|
|
46941bad3f | ||
|
|
1c9ef49f68 | ||
|
|
9fdf00c334 | ||
|
|
8b84c33e1f | ||
|
|
9df3061579 | ||
|
|
35cf807f80 | ||
|
|
b71586c8fc | ||
|
|
25beeef21f | ||
|
|
ab98621835 | ||
|
|
57b6f8699a | ||
|
|
639928047f | ||
|
|
b0d596d6ec | ||
|
|
516ab94fda | ||
|
|
7ee7beacff | ||
|
|
a57df29245 | ||
|
|
8079432816 | ||
|
|
417d0fc033 | ||
|
|
c6b334cb9d | ||
|
|
5fb3629aaf | ||
|
|
6e0b7463be | ||
|
|
7855d484a9 | ||
|
|
9dbc45f2c8 | ||
|
|
a01c39e548 | ||
|
|
a723380469 | ||
|
|
e9adb55941 | ||
|
|
d349f53a2b | ||
|
|
dba61648b9 | ||
|
|
f4c377ee40 | ||
|
|
ae111e5c37 | ||
|
|
456906d8d5 | ||
|
|
9d3fe34aff | ||
|
|
365293836a | ||
|
|
02d08d817b | ||
|
|
3644894e3a | ||
|
|
89a48db387 | ||
|
|
0f925ecaa0 | ||
|
|
f849d12184 | ||
|
|
3f7b083f87 | ||
|
|
0d6e8999f3 | ||
|
|
13b2080d21 | ||
|
|
d2c998fed8 | ||
|
|
8252d7d97a | ||
|
|
866bf46223 | ||
|
|
7495affd99 | ||
|
|
a92ba998e9 | ||
|
|
bd82aeabeb | ||
|
|
e52b186c6d | ||
|
|
9bd8f0e8a5 | ||
|
|
5215d47c1f | ||
|
|
4ff3cf4653 | ||
|
|
c687e3e917 | ||
|
|
b21c0f3623 | ||
|
|
bb02497b02 | ||
|
|
23c47b525d | ||
|
|
a7cd6e9310 | ||
|
|
22ead50285 | ||
|
|
564c548457 | ||
|
|
aa73af816c | ||
|
|
0ec42e0e7f | ||
|
|
136271bb51 | ||
|
|
f7e9dcb915 | ||
|
|
b344ac4e03 | ||
|
|
c79fd92f1e | ||
|
|
fd19681562 | ||
|
|
92897d9408 | ||
|
|
a1f298561f | ||
|
|
46393c3d31 | ||
|
|
2a76fd6436 | ||
|
|
ea0649cd71 | ||
|
|
710e7046c5 | ||
|
|
f5e46ad721 | ||
|
|
795a357fdf | ||
|
|
74dfd7d07b | ||
|
|
976b9cd58b | ||
|
|
e6f96722c9 | ||
|
|
25d12a4bd1 | ||
|
|
8ed1b40fad | ||
|
|
ddb13bd60a | ||
|
|
14689bb22e | ||
|
|
19790c228d | ||
|
|
0922edb17f | ||
|
|
3d453363f9 | ||
|
|
1ac19ff900 | ||
|
|
8cfc4af14b | ||
|
|
3431eeac37 | ||
|
|
53220acab2 | ||
|
|
0f0e9c02bc | ||
|
|
579cf26d1a | ||
|
|
0624f28646 | ||
|
|
803db4b524 | ||
|
|
977bec14e8 | ||
|
|
01322afd8d | ||
|
|
829440ffe5 | ||
|
|
8a25100307 | ||
|
|
89935a5522 | ||
|
|
7f5b05c3ce | ||
|
|
d77ac9e05a | ||
|
|
e3049f0fba | ||
|
|
a936bce13c | ||
|
|
07b4ce0427 | ||
|
|
78202919bb | ||
|
|
40a8927284 | ||
|
|
463ad64344 | ||
|
|
4437ab2d52 | ||
|
|
413ae91253 | ||
|
|
6dd09adcff | ||
|
|
4d9d5e19a0 | ||
|
|
1d55c958da | ||
|
|
0f17f81017 | ||
|
|
38f5c0325c | ||
|
|
993fab396f | ||
|
|
7ae493bdab | ||
|
|
d1bbbec44e | ||
|
|
68f3764e40 | ||
|
|
da8d94e053 | ||
|
|
2dc5b5308f | ||
|
|
192e9e7f89 | ||
|
|
094d67fd1c | ||
|
|
1e4455e0a7 | ||
|
|
4f990b5b5a | ||
|
|
53c2147059 | ||
|
|
2a457bc364 | ||
|
|
a535bd7077 | ||
|
|
453639e7b5 | ||
|
|
cd4e4cd633 | ||
|
|
6afcb44de9 | ||
|
|
8a2ed4c253 | ||
|
|
615113b9ca | ||
|
|
50a53685fc | ||
|
|
58816f40bb | ||
|
|
993fe84341 | ||
|
|
4791685014 | ||
|
|
217a316862 | ||
|
|
0e23d47e8d | ||
|
|
4a7658813f | ||
|
|
09d98db314 | ||
|
|
f58511decc | ||
|
|
8d3a141d13 | ||
|
|
a0e95faa62 | ||
|
|
96f3f66288 | ||
|
|
6b3f3a4469 | ||
|
|
d169ab7a71 | ||
|
|
bd660d354c | ||
|
|
d892b142bd | ||
|
|
b281941703 | ||
|
|
96a3536ced | ||
|
|
cdb4f7e8b2 | ||
|
|
f8ab805545 | ||
|
|
687cdf2005 | ||
|
|
80f8797bc2 | ||
|
|
39dd07080f | ||
|
|
f5e3956907 | ||
|
|
8a51434ce3 | ||
|
|
cc3d11c85f | ||
|
|
f87298025e | ||
|
|
7087341848 | ||
|
|
8a1bda14c4 | ||
|
|
46891c2c66 | ||
|
|
c04f30e264 | ||
|
|
67a9354a6a | ||
|
|
59817f079d | ||
|
|
83a56489d4 | ||
|
|
aa0e00168a | ||
|
|
7365a5ab62 | ||
|
|
2b476ad399 | ||
|
|
2acfb70def | ||
|
|
00c24c078c | ||
|
|
6a4a0340d7 | ||
|
|
ab34471863 | ||
|
|
aab1b6280d | ||
|
|
16b639b359 | ||
|
|
cec9c68d38 | ||
|
|
21b4438a81 | ||
|
|
c0fcac3eca | ||
|
|
70479fb272 | ||
|
|
fc87247629 | ||
|
|
a6f8843aa9 | ||
|
|
1d62ae31fc | ||
|
|
0afaec14c5 | ||
|
|
9244c76008 | ||
|
|
bf2e48f84f | ||
|
|
b7f826c220 | ||
|
|
7480434d42 | ||
|
|
3c47183378 | ||
|
|
7ffbb7d7b3 | ||
|
|
7c1237a018 | ||
|
|
3be7e35f14 | ||
|
|
31d9919f01 | ||
|
|
9e73156f2c | ||
|
|
e730db76c2 | ||
|
|
0ece275453 | ||
|
|
94955af8b9 | ||
|
|
26ff8371ab | ||
|
|
50bc140835 | ||
|
|
f377083a3d | ||
|
|
f44a07f3b8 |
5
.github/ISSUE_TEMPLATE.md
vendored
5
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,5 +0,0 @@
|
||||
<!--
|
||||
IMPORTANT: Please open an issue ONLY if you find something wrong with the source code. For questions and feedback use Jesse Forum:
|
||||
|
||||
https://forum.jesse.trade/
|
||||
-->
|
||||
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
IMPORTANT: Please open an issue ONLY if you find something wrong with the source code. For questions and feedback use Discord (https://jesse.trade/discord). Also make sure to give the documentation (https://docs.jesse.trade/) and FAQ (https://jesse.trade/help) a good read to eliminate the possibility of causing the problem due to wrong usage. Make sure you are using the most recent version `pip show jesse` and updated all requirements `pip install -r https://raw.githubusercontent.com/jesse-ai/jesse/master/requirements.txt`.
|
||||
-->
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is. Include the whole error message / traceback.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Include your routes.py, config.py (make sure to remove personal information) and if possible your strategy code (if you want to keep it private - contact us on Discord directly).
|
||||
2. Explain the steps you do that lead to the error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Enviroment (please complete the following information):**
|
||||
- OS: [e.g. iOS, Windows, Ubuntu]
|
||||
- Version [use `pip show jesse`]
|
||||
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!---
|
||||
Make sure to check the roadmap (https://docs.jesse.trade/docs/roadmap.html) and the Trello boards linked there whether your idea is already listed and give Jesse's documentation a good read to make sure you don't request something that's already possible. If possible use Discord (https://jesse.trade/discord) to discuss your ideas with the community.
|
||||
-->
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
17
.github/stale.yml
vendored
Normal file
17
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 60
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
98
.github/workflows/codeql-analysis.yml
vendored
Normal file
98
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '0 0 1 * *'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'python' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ matrix.path }}
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
- name: Install ta-lib
|
||||
run: |
|
||||
if ([ "$RUNNER_OS" = "macOS" ]); then
|
||||
brew install ta-lib
|
||||
fi
|
||||
if ([ "$RUNNER_OS" = "Linux" ]); then
|
||||
if [ ! -f "$GITHUB_WORKSPACE/ta-lib/src" ]; then wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz -q && tar -xzf ta-lib-0.4.0-src.tar.gz; fi
|
||||
cd ta-lib/
|
||||
./configure --prefix=/usr
|
||||
if [ ! -f "$HOME/ta-lib/src" ]; then make; fi
|
||||
sudo make install
|
||||
cd
|
||||
fi
|
||||
if ([ "$RUNNER_OS" = "Windows" ]); then
|
||||
curl -sL http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-msvc.zip -o $GITHUB_WORKSPACE/ta-lib.zip --create-dirs && 7z x $GITHUB_WORKSPACE/ta-lib.zip -o/c/ta-lib && mv /c/ta-lib/ta-lib/* /c/ta-lib/ && rm -rf /c/ta-lib/ta-lib && cd /c/ta-lib/c/make/cdr/win32/msvc && nmake
|
||||
fi
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||
pip install numba
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
97
.github/workflows/python-package.yml
vendored
Normal file
97
.github/workflows/python-package.yml
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
# This workflow will install Python dependencies, run tests and lint with a single version of Python
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
|
||||
|
||||
name: Python application
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ${{matrix.os}}
|
||||
strategy:
|
||||
matrix:
|
||||
# os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
path: ~/.cache/pip
|
||||
#- os: macos-latest
|
||||
# path: ~/Library/Caches/pip
|
||||
#- os: windows-latest
|
||||
# path: ~\AppData\Local\pip\Cache
|
||||
python-version: [3.8, 3.9, '3.10']
|
||||
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v2
|
||||
id: cache
|
||||
with:
|
||||
path: ${{ matrix.path }}
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
- name: Set up ta-lib dir
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
run: |
|
||||
mkdir -p $HOME/.local/ta-lib
|
||||
echo "LD_LIBRARY_PATH=$HOME/.local/ta-lib/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV
|
||||
echo "TA_INCLUDE_PATH=$HOME/.local/ta-lib/include" >> $GITHUB_ENV
|
||||
echo "TA_LIBRARY_PATH=$HOME/.local/ta-lib/lib" >> $GITHUB_ENV
|
||||
- name: Set up ta-lib cache
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
uses: actions/cache@v2
|
||||
id: talib-cache
|
||||
with:
|
||||
path: |
|
||||
~/.local/ta-lib/lib
|
||||
~/.local/ta-lib/include
|
||||
key: talib-cache-v0.4.0
|
||||
- name: Install ta-lib mac / windows
|
||||
run: |
|
||||
if ([ "$RUNNER_OS" = "macOS" ]); then
|
||||
brew install ta-lib
|
||||
fi
|
||||
if ([ "$RUNNER_OS" = "Windows" ]); then
|
||||
curl -sL http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-msvc.zip -o $GITHUB_WORKSPACE/ta-lib.zip --create-dirs && 7z x $GITHUB_WORKSPACE/ta-lib.zip -o/c/ta-lib && mv /c/ta-lib/ta-lib/* /c/ta-lib/ && rm -rf /c/ta-lib/ta-lib && cd /c/ta-lib/c/make/cdr/win32/msvc && nmake
|
||||
fi
|
||||
- name: Install ta-lib Linux
|
||||
if: steps.talib-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz -q && tar -xzf ta-lib-0.4.0-src.tar.gz
|
||||
cd ta-lib/
|
||||
./configure --prefix=$HOME/.local/ta-lib
|
||||
make
|
||||
sudo make install
|
||||
cd
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||
if [ ! ${{ matrix.python-version }} = "3.10"]; then pip install numba; fi
|
||||
pip install -e . -U
|
||||
# - name: Lint with flake8
|
||||
# run: |
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
# flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
# flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
pip install pytest
|
||||
pytest
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -147,3 +147,5 @@ cython_debug/
|
||||
/storage/*.gz
|
||||
/.vagrant
|
||||
testing-*.py
|
||||
/storage/full-reports/*.html
|
||||
/storage/logs/*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
language: python
|
||||
dist: bionic
|
||||
dist: focal
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/pip
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
FROM python:3.9.0
|
||||
FROM python:3.9-slim
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get -y install build-essential libssl-dev \
|
||||
&& apt-get -y install git build-essential libssl-dev \
|
||||
&& apt-get clean \
|
||||
&& pip install --upgrade pip
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM python:3.9.0
|
||||
FROM python:3.9-slim
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
RUN apt-get update \
|
||||
|
||||
2
MANIFEST.in
Normal file
2
MANIFEST.in
Normal file
@@ -0,0 +1,2 @@
|
||||
include jesse/static/*
|
||||
include jesse/static/**/*
|
||||
13
README.md
13
README.md
@@ -8,8 +8,8 @@
|
||||
|
||||
[](https://jesse.trade)
|
||||
[](https://docs.jesse.trade)
|
||||
[](https://jesse.trade/discord)
|
||||
[](https://forum.jesse.trade)
|
||||
[](https://jesse.trade/help)
|
||||
[](https://jesse.trade/discord)
|
||||
[](https://jesse.trade/blog)
|
||||
---
|
||||
Jesse is an advanced crypto trading framework which aims to simplify researching and defining trading strategies.
|
||||
@@ -20,6 +20,11 @@ In fact, it is so simple that in case you already know Python, you can get start
|
||||
|
||||
[Here](https://docs.jesse.trade/docs/) you can read more about why Jesse's features.
|
||||
|
||||
## Sponsored Promotion
|
||||
|
||||
[](https://tokenbot.com/?utm_source=github&utm_medium=jesse&utm_campaign=algodevs)
|
||||
|
||||
|
||||
## Getting Started
|
||||
Head over to the "getting started" section of the [documentation](https://docs.jesse.trade/docs/getting-started). The
|
||||
documentation is short yet very informative.
|
||||
@@ -82,9 +87,7 @@ And here are generated charts:
|
||||
This is the very initial release. There's way more. Subscribe to our mailing list at [jesse.trade](https://jesse.trade) to get the good stuff as soon they're released. Don't worry, We won't send you spam. Pinky promise.
|
||||
|
||||
## Community
|
||||
We've created a [community](http://forum.jesse.trade/) for Jesse users to discuss algo-trading. It's a warm place to share ideas, and help each other out.
|
||||
|
||||
**[update]:** We now have a [discord server](https://discord.gg/nztUFbMnF5) to discuss Jesse and trading in general. Make sure to join.
|
||||
I created a [discord server](https://jesse.trade/discord) for Jesse users to discuss algo-trading. It's a warm place to share ideas, and help each other out.
|
||||
|
||||
## How to contribute
|
||||
Thank you for your interest in contributing to the project. Before starting to work on a PR, please make sure that it isn't under the "in progress" column in our [Github project page](https://github.com/jesse-ai/jesse/projects/2). In case you want to help but don't know what tasks we need help for, checkout the "todo" column.
|
||||
|
||||
BIN
assets/TokenBot-Jesse-banner.png
Normal file
BIN
assets/TokenBot-Jesse-banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 122 KiB |
@@ -1,244 +1,181 @@
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
# Hide the "FutureWarning: pandas.util.testing is deprecated." caused by empyrical
|
||||
import warnings
|
||||
from pydoc import locate
|
||||
|
||||
from typing import Optional
|
||||
import click
|
||||
import pkg_resources
|
||||
|
||||
from fastapi import BackgroundTasks, Query, Header
|
||||
from starlette.websockets import WebSocket, WebSocketDisconnect
|
||||
from fastapi.responses import JSONResponse, FileResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from jesse.services import auth as authenticator
|
||||
from jesse.services.redis import async_redis, async_publish, sync_publish
|
||||
from jesse.services.web import fastapi_app, BacktestRequestJson, ImportCandlesRequestJson, CancelRequestJson, \
|
||||
LoginRequestJson, ConfigRequestJson, LoginJesseTradeRequestJson, NewStrategyRequestJson, FeedbackRequestJson, \
|
||||
ReportExceptionRequestJson, OptimizationRequestJson
|
||||
import uvicorn
|
||||
from asyncio import Queue
|
||||
import jesse.helpers as jh
|
||||
import time
|
||||
|
||||
# to silent stupid pandas warnings
|
||||
warnings.simplefilter(action='ignore', category=FutureWarning)
|
||||
|
||||
# Python version validation.
|
||||
if jh.python_version() < 3.7:
|
||||
print(
|
||||
jh.color(
|
||||
'Jesse requires Python version above 3.7. Yours is {}'.format(jh.python_version()),
|
||||
'red'
|
||||
)
|
||||
)
|
||||
|
||||
# fix directory issue
|
||||
sys.path.insert(0, os.getcwd())
|
||||
|
||||
ls = os.listdir('.')
|
||||
is_jesse_project = 'strategies' in ls and 'config.py' in ls and 'storage' in ls and 'routes.py' in ls
|
||||
# variable to know if the live trade plugin is installed
|
||||
HAS_LIVE_TRADE_PLUGIN = True
|
||||
try:
|
||||
import jesse_live
|
||||
except ModuleNotFoundError:
|
||||
HAS_LIVE_TRADE_PLUGIN = False
|
||||
|
||||
|
||||
def validate_cwd() -> None:
|
||||
"""
|
||||
make sure we're in a Jesse project
|
||||
"""
|
||||
if not is_jesse_project:
|
||||
if not jh.is_jesse_project():
|
||||
print(
|
||||
jh.color(
|
||||
'Current directory is not a Jesse project. You must run commands from the root of a Jesse project.',
|
||||
'Current directory is not a Jesse project. You must run commands from the root of a Jesse project. Read this page for more info: https://docs.jesse.trade/docs/getting-started/#create-a-new-jesse-project',
|
||||
'red'
|
||||
)
|
||||
)
|
||||
os._exit(1)
|
||||
|
||||
|
||||
def inject_local_config() -> None:
|
||||
"""
|
||||
injects config from local config file
|
||||
"""
|
||||
local_config = locate('config.config')
|
||||
from jesse.config import set_config
|
||||
set_config(local_config)
|
||||
# print(os.path.dirname(jesse))
|
||||
JESSE_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
|
||||
def inject_local_routes() -> None:
|
||||
"""
|
||||
injects routes from local routes folder
|
||||
"""
|
||||
local_router = locate('routes')
|
||||
from jesse.routes import router
|
||||
|
||||
router.set_routes(local_router.routes)
|
||||
router.set_extra_candles(local_router.extra_candles)
|
||||
# load homepage
|
||||
@fastapi_app.get("/")
|
||||
async def index():
|
||||
return FileResponse(f"{JESSE_DIR}/static/index.html")
|
||||
|
||||
|
||||
# inject local files
|
||||
if is_jesse_project:
|
||||
inject_local_config()
|
||||
inject_local_routes()
|
||||
@fastapi_app.post("/terminate-all")
|
||||
async def terminate_all(authorization: Optional[str] = Header(None)):
|
||||
if not authenticator.is_valid_token(authorization):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
from jesse.services.multiprocessing import process_manager
|
||||
|
||||
process_manager.flush()
|
||||
return JSONResponse({'message': 'terminating all tasks...'})
|
||||
|
||||
|
||||
def register_custom_exception_handler() -> None:
|
||||
"""
|
||||
@fastapi_app.post("/shutdown")
|
||||
async def shutdown(background_tasks: BackgroundTasks, authorization: Optional[str] = Header(None)):
|
||||
if not authenticator.is_valid_token(authorization):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
:return:
|
||||
"""
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
import logging
|
||||
from jesse.services import logger as jesse_logger
|
||||
import click
|
||||
from jesse import exceptions
|
||||
background_tasks.add_task(jh.terminate_app)
|
||||
return JSONResponse({'message': 'Shutting down...'})
|
||||
|
||||
log_format = "%(message)s"
|
||||
os.makedirs('storage/logs', exist_ok=True)
|
||||
|
||||
if jh.is_livetrading():
|
||||
logging.basicConfig(filename='storage/logs/live-trade.txt', level=logging.INFO,
|
||||
filemode='w', format=log_format)
|
||||
elif jh.is_paper_trading():
|
||||
logging.basicConfig(filename='storage/logs/paper-trade.txt', level=logging.INFO,
|
||||
filemode='w',
|
||||
format=log_format)
|
||||
elif jh.is_collecting_data():
|
||||
logging.basicConfig(filename='storage/logs/collect.txt', level=logging.INFO, filemode='w',
|
||||
format=log_format)
|
||||
elif jh.is_optimizing():
|
||||
logging.basicConfig(filename='storage/logs/optimize.txt', level=logging.INFO, filemode='w',
|
||||
format=log_format)
|
||||
else:
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
@fastapi_app.post("/auth")
|
||||
def auth(json_request: LoginRequestJson):
|
||||
return authenticator.password_to_token(json_request.password)
|
||||
|
||||
# main thread
|
||||
def handle_exception(exc_type, exc_value, exc_traceback) -> None:
|
||||
if issubclass(exc_type, KeyboardInterrupt):
|
||||
sys.excepthook(exc_type, exc_value, exc_traceback)
|
||||
return
|
||||
|
||||
# handle Breaking exceptions
|
||||
if exc_type in [
|
||||
exceptions.InvalidConfig, exceptions.RouteNotFound, exceptions.InvalidRoutes,
|
||||
exceptions.CandleNotFoundInDatabase
|
||||
]:
|
||||
click.clear()
|
||||
print('=' * 30 + ' EXCEPTION TRACEBACK:')
|
||||
traceback.print_tb(exc_traceback, file=sys.stdout)
|
||||
print("=" * 73)
|
||||
print(
|
||||
'\n',
|
||||
jh.color('Uncaught Exception:', 'red'),
|
||||
jh.color('{}: {}'.format(exc_type.__name__, exc_value), 'yellow')
|
||||
)
|
||||
return
|
||||
@fastapi_app.post("/make-strategy")
|
||||
def make_strategy(json_request: NewStrategyRequestJson, authorization: Optional[str] = Header(None)) -> JSONResponse:
|
||||
if not authenticator.is_valid_token(authorization):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
# send notifications if it's a live session
|
||||
if jh.is_live():
|
||||
jesse_logger.error(
|
||||
'{}: {}'.format(exc_type.__name__, exc_value)
|
||||
from jesse.services import strategy_maker
|
||||
return strategy_maker.generate(json_request.name)
|
||||
|
||||
|
||||
@fastapi_app.post("/feedback")
|
||||
def feedback(json_request: FeedbackRequestJson, authorization: Optional[str] = Header(None)) -> JSONResponse:
|
||||
if not authenticator.is_valid_token(authorization):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
from jesse.services import jesse_trade
|
||||
return jesse_trade.feedback(json_request.description, json_request.email)
|
||||
|
||||
|
||||
@fastapi_app.post("/report-exception")
|
||||
def report_exception(json_request: ReportExceptionRequestJson, authorization: Optional[str] = Header(None)) -> JSONResponse:
|
||||
if not authenticator.is_valid_token(authorization):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
from jesse.services import jesse_trade
|
||||
return jesse_trade.report_exception(
|
||||
json_request.description,
|
||||
json_request.traceback,
|
||||
json_request.mode,
|
||||
json_request.attach_logs,
|
||||
json_request.session_id,
|
||||
json_request.email,
|
||||
has_live=HAS_LIVE_TRADE_PLUGIN
|
||||
)
|
||||
|
||||
|
||||
@fastapi_app.post("/get-config")
|
||||
def get_config(json_request: ConfigRequestJson, authorization: Optional[str] = Header(None)):
|
||||
if not authenticator.is_valid_token(authorization):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
from jesse.modes.data_provider import get_config as gc
|
||||
|
||||
return JSONResponse({
|
||||
'data': gc(json_request.current_config, has_live=HAS_LIVE_TRADE_PLUGIN)
|
||||
}, status_code=200)
|
||||
|
||||
|
||||
@fastapi_app.post("/update-config")
|
||||
def update_config(json_request: ConfigRequestJson, authorization: Optional[str] = Header(None)):
|
||||
if not authenticator.is_valid_token(authorization):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
from jesse.modes.data_provider import update_config as uc
|
||||
|
||||
uc(json_request.current_config)
|
||||
|
||||
return JSONResponse({'message': 'Updated configurations successfully'}, status_code=200)
|
||||
|
||||
|
||||
@fastapi_app.websocket("/ws")
|
||||
async def websocket_endpoint(websocket: WebSocket, token: str = Query(...)):
|
||||
from jesse.services.multiprocessing import process_manager
|
||||
from jesse.services.env import ENV_VALUES
|
||||
|
||||
if not authenticator.is_valid_token(token):
|
||||
return
|
||||
|
||||
await websocket.accept()
|
||||
|
||||
queue = Queue()
|
||||
ch, = await async_redis.psubscribe(f"{ENV_VALUES['APP_PORT']}:channel:*")
|
||||
|
||||
async def echo(q):
|
||||
while True:
|
||||
msg = await q.get()
|
||||
msg = json.loads(msg)
|
||||
msg['id'] = process_manager.get_client_id(msg['id'])
|
||||
await websocket.send_json(
|
||||
msg
|
||||
)
|
||||
|
||||
if jh.is_live() or jh.is_collecting_data():
|
||||
logging.error("Uncaught Exception:", exc_info=(exc_type, exc_value, exc_traceback))
|
||||
else:
|
||||
print('=' * 30 + ' EXCEPTION TRACEBACK:')
|
||||
traceback.print_tb(exc_traceback, file=sys.stdout)
|
||||
print("=" * 73)
|
||||
print(
|
||||
'\n',
|
||||
jh.color('Uncaught Exception:', 'red'),
|
||||
jh.color('{}: {}'.format(exc_type.__name__, exc_value), 'yellow')
|
||||
)
|
||||
async def reader(channel, q):
|
||||
async for ch, message in channel.iter():
|
||||
# modify id and set the one that the font-end knows
|
||||
await q.put(message)
|
||||
|
||||
if jh.is_paper_trading():
|
||||
print(
|
||||
jh.color(
|
||||
'An uncaught exception was raised. Check the log file at:\n{}'.format(
|
||||
'storage/logs/paper-trade.txt'
|
||||
),
|
||||
'red'
|
||||
)
|
||||
)
|
||||
elif jh.is_livetrading():
|
||||
print(
|
||||
jh.color(
|
||||
'An uncaught exception was raised. Check the log file at:\n{}'.format(
|
||||
'storage/logs/live-trade.txt'
|
||||
),
|
||||
'red'
|
||||
)
|
||||
)
|
||||
elif jh.is_collecting_data():
|
||||
print(
|
||||
jh.color(
|
||||
'An uncaught exception was raised. Check the log file at:\n{}'.format(
|
||||
'storage/logs/collect.txt'
|
||||
),
|
||||
'red'
|
||||
)
|
||||
)
|
||||
asyncio.get_running_loop().create_task(reader(ch, queue))
|
||||
asyncio.get_running_loop().create_task(echo(queue))
|
||||
|
||||
sys.excepthook = handle_exception
|
||||
|
||||
# other threads
|
||||
if jh.python_version() >= 3.8:
|
||||
def handle_thread_exception(args) -> None:
|
||||
if args.exc_type == SystemExit:
|
||||
return
|
||||
|
||||
# handle Breaking exceptions
|
||||
if args.exc_type in [
|
||||
exceptions.InvalidConfig, exceptions.RouteNotFound, exceptions.InvalidRoutes,
|
||||
exceptions.CandleNotFoundInDatabase
|
||||
]:
|
||||
click.clear()
|
||||
print('=' * 30 + ' EXCEPTION TRACEBACK:')
|
||||
traceback.print_tb(args.exc_traceback, file=sys.stdout)
|
||||
print("=" * 73)
|
||||
print(
|
||||
'\n',
|
||||
jh.color('Uncaught Exception:', 'red'),
|
||||
jh.color('{}: {}'.format(args.exc_type.__name__, args.exc_value), 'yellow')
|
||||
)
|
||||
return
|
||||
|
||||
# send notifications if it's a live session
|
||||
if jh.is_live():
|
||||
jesse_logger.error(
|
||||
'{}: {}'.format(args.exc_type.__name__, args.exc_value)
|
||||
)
|
||||
|
||||
if jh.is_live() or jh.is_collecting_data():
|
||||
logging.error("Uncaught Exception:",
|
||||
exc_info=(args.exc_type, args.exc_value, args.exc_traceback))
|
||||
else:
|
||||
print('=' * 30 + ' EXCEPTION TRACEBACK:')
|
||||
traceback.print_tb(args.exc_traceback, file=sys.stdout)
|
||||
print("=" * 73)
|
||||
print(
|
||||
'\n',
|
||||
jh.color('Uncaught Exception:', 'red'),
|
||||
jh.color('{}: {}'.format(args.exc_type.__name__, args.exc_value), 'yellow')
|
||||
)
|
||||
|
||||
if jh.is_paper_trading():
|
||||
print(
|
||||
jh.color(
|
||||
'An uncaught exception was raised. Check the log file at:\n{}'.format(
|
||||
'storage/logs/paper-trade.txt'
|
||||
),
|
||||
'red'
|
||||
)
|
||||
)
|
||||
elif jh.is_livetrading():
|
||||
print(
|
||||
jh.color(
|
||||
'An uncaught exception was raised. Check the log file at:\n{}'.format(
|
||||
'storage/logs/live-trade.txt'
|
||||
),
|
||||
'red'
|
||||
)
|
||||
)
|
||||
elif jh.is_collecting_data():
|
||||
print(
|
||||
jh.color(
|
||||
'An uncaught exception was raised. Check the log file at:\n{}'.format(
|
||||
'storage/logs/collect.txt'
|
||||
),
|
||||
'red'
|
||||
)
|
||||
)
|
||||
|
||||
threading.excepthook = handle_thread_exception
|
||||
try:
|
||||
while True:
|
||||
# just so WebSocketDisconnect would be raised on connection close
|
||||
await websocket.receive_text()
|
||||
except WebSocketDisconnect:
|
||||
await async_redis.punsubscribe(f"{ENV_VALUES['APP_PORT']}:channel:*")
|
||||
print('Websocket disconnected')
|
||||
|
||||
|
||||
# create a Click group
|
||||
@@ -249,251 +186,310 @@ def cli() -> None:
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument('exchange', required=True, type=str)
|
||||
@click.argument('symbol', required=True, type=str)
|
||||
@click.argument('start_date', required=True, type=str)
|
||||
@click.option('--skip-confirmation', is_flag=True,
|
||||
help="Will prevent confirmation for skipping duplicates")
|
||||
def import_candles(exchange: str, symbol: str, start_date: str, skip_confirmation: bool) -> None:
|
||||
"""
|
||||
imports historical candles from exchange
|
||||
"""
|
||||
@click.option(
|
||||
'--strict/--no-strict', default=True,
|
||||
help='Default is the strict mode which will raise an exception if the values for license is not set.'
|
||||
)
|
||||
def install_live(strict: bool) -> None:
|
||||
from jesse.services.installer import install
|
||||
install(HAS_LIVE_TRADE_PLUGIN, strict)
|
||||
|
||||
|
||||
@cli.command()
|
||||
def run() -> None:
|
||||
validate_cwd()
|
||||
from jesse.config import config
|
||||
config['app']['trading_mode'] = 'import-candles'
|
||||
|
||||
register_custom_exception_handler()
|
||||
# run all the db migrations
|
||||
from jesse.services.migrator import run as run_migrations
|
||||
import peewee
|
||||
try:
|
||||
run_migrations()
|
||||
except peewee.OperationalError:
|
||||
sleep_seconds = 10
|
||||
print(f"Database wasn't ready. Sleep for {sleep_seconds} seconds and try again.")
|
||||
time.sleep(sleep_seconds)
|
||||
run_migrations()
|
||||
|
||||
from jesse.services import db
|
||||
# read port from .env file, if not found, use default
|
||||
from jesse.services.env import ENV_VALUES
|
||||
if 'APP_PORT' in ENV_VALUES:
|
||||
port = int(ENV_VALUES['APP_PORT'])
|
||||
else:
|
||||
port = 9000
|
||||
|
||||
# run the main application
|
||||
uvicorn.run(fastapi_app, host="0.0.0.0", port=port, log_level="info")
|
||||
|
||||
|
||||
@fastapi_app.post('/general-info')
|
||||
def general_info(authorization: Optional[str] = Header(None)) -> JSONResponse:
|
||||
if not authenticator.is_valid_token(authorization):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
from jesse.modes import data_provider
|
||||
|
||||
try:
|
||||
data = data_provider.get_general_info(has_live=HAS_LIVE_TRADE_PLUGIN)
|
||||
except Exception as e:
|
||||
return JSONResponse({
|
||||
'error': str(e)
|
||||
}, status_code=500)
|
||||
|
||||
return JSONResponse(
|
||||
data,
|
||||
status_code=200
|
||||
)
|
||||
|
||||
|
||||
@fastapi_app.post('/import-candles')
|
||||
def import_candles(request_json: ImportCandlesRequestJson, authorization: Optional[str] = Header(None)) -> JSONResponse:
|
||||
from jesse.services.multiprocessing import process_manager
|
||||
|
||||
validate_cwd()
|
||||
|
||||
if not authenticator.is_valid_token(authorization):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
from jesse.modes import import_candles_mode
|
||||
|
||||
import_candles_mode.run(exchange, symbol, start_date, skip_confirmation)
|
||||
process_manager.add_task(
|
||||
import_candles_mode.run, 'candles-' + str(request_json.id), request_json.exchange, request_json.symbol,
|
||||
request_json.start_date, True
|
||||
)
|
||||
|
||||
db.close_connection()
|
||||
return JSONResponse({'message': 'Started importing candles...'}, status_code=202)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument('start_date', required=True, type=str)
|
||||
@click.argument('finish_date', required=True, type=str)
|
||||
@click.option('--debug/--no-debug', default=False,
|
||||
help='Displays logging messages instead of the progressbar. Used for debugging your strategy.')
|
||||
@click.option('--csv/--no-csv', default=False,
|
||||
help='Outputs a CSV file of all executed trades on completion.')
|
||||
@click.option('--json/--no-json', default=False,
|
||||
help='Outputs a JSON file of all executed trades on completion.')
|
||||
@click.option('--fee/--no-fee', default=True,
|
||||
help='You can use "--no-fee" as a quick way to set trading fee to zero.')
|
||||
@click.option('--chart/--no-chart', default=False,
|
||||
help='Generates charts of daily portfolio balance and assets price change. Useful for a visual comparision of your portfolio against the market.')
|
||||
@click.option('--tradingview/--no-tradingview', default=False,
|
||||
help="Generates an output that can be copy-and-pasted into tradingview.com's pine-editor too see the trades in their charts.")
|
||||
def backtest(start_date: str, finish_date: str, debug: bool, csv: bool, json: bool, fee: bool, chart: bool,
|
||||
tradingview: bool) -> None:
|
||||
"""
|
||||
backtest mode. Enter in "YYYY-MM-DD" "YYYY-MM-DD"
|
||||
"""
|
||||
@fastapi_app.delete("/import-candles")
|
||||
def cancel_import_candles(request_json: CancelRequestJson, authorization: Optional[str] = Header(None)):
|
||||
from jesse.services.multiprocessing import process_manager
|
||||
|
||||
if not authenticator.is_valid_token(authorization):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
process_manager.cancel_process('candles-' + request_json.id)
|
||||
|
||||
return JSONResponse({'message': f'Candles process with ID of {request_json.id} was requested for termination'}, status_code=202)
|
||||
|
||||
|
||||
@fastapi_app.post("/backtest")
|
||||
def backtest(request_json: BacktestRequestJson, authorization: Optional[str] = Header(None)):
|
||||
from jesse.services.multiprocessing import process_manager
|
||||
|
||||
if not authenticator.is_valid_token(authorization):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
validate_cwd()
|
||||
|
||||
from jesse.config import config
|
||||
config['app']['trading_mode'] = 'backtest'
|
||||
from jesse.modes.backtest_mode import run as run_backtest
|
||||
|
||||
register_custom_exception_handler()
|
||||
process_manager.add_task(
|
||||
run_backtest,
|
||||
'backtest-' + str(request_json.id),
|
||||
request_json.debug_mode,
|
||||
request_json.config,
|
||||
request_json.routes,
|
||||
request_json.extra_routes,
|
||||
request_json.start_date,
|
||||
request_json.finish_date,
|
||||
None,
|
||||
request_json.export_chart,
|
||||
request_json.export_tradingview,
|
||||
request_json.export_full_reports,
|
||||
request_json.export_csv,
|
||||
request_json.export_json
|
||||
)
|
||||
|
||||
from jesse.services import db
|
||||
from jesse.modes import backtest_mode
|
||||
from jesse.services.selectors import get_exchange
|
||||
|
||||
# debug flag
|
||||
config['app']['debug_mode'] = debug
|
||||
|
||||
# fee flag
|
||||
if not fee:
|
||||
for e in config['app']['trading_exchanges']:
|
||||
config['env']['exchanges'][e]['fee'] = 0
|
||||
get_exchange(e).fee = 0
|
||||
|
||||
backtest_mode.run(start_date, finish_date, chart=chart, tradingview=tradingview, csv=csv,
|
||||
json=json)
|
||||
|
||||
db.close_connection()
|
||||
return JSONResponse({'message': 'Started backtesting...'}, status_code=202)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument('start_date', required=True, type=str)
|
||||
@click.argument('finish_date', required=True, type=str)
|
||||
@click.argument('optimal_total', required=True, type=int)
|
||||
@click.option(
|
||||
'--cpu', default=0, show_default=True,
|
||||
help='The number of CPU cores that Jesse is allowed to use. If set to 0, it will use as many as is available on your machine.')
|
||||
@click.option(
|
||||
'--debug/--no-debug', default=False,
|
||||
help='Displays detailed logs about the genetics algorithm. Use it if you are interested int he genetics algorithm.'
|
||||
)
|
||||
@click.option('--csv/--no-csv', default=False, help='Outputs a CSV file of all DNAs on completion.')
|
||||
@click.option('--json/--no-json', default=False, help='Outputs a JSON file of all DNAs on completion.')
|
||||
def optimize(start_date: str, finish_date: str, optimal_total: int, cpu: int, debug: bool, csv: bool,
|
||||
json: bool) -> None:
|
||||
"""
|
||||
tunes the hyper-parameters of your strategy
|
||||
"""
|
||||
@fastapi_app.post("/optimization")
|
||||
async def optimization(request_json: OptimizationRequestJson, authorization: Optional[str] = Header(None)):
|
||||
if not authenticator.is_valid_token(authorization):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
from jesse.services.multiprocessing import process_manager
|
||||
|
||||
validate_cwd()
|
||||
from jesse.config import config
|
||||
config['app']['trading_mode'] = 'optimize'
|
||||
|
||||
register_custom_exception_handler()
|
||||
from jesse.modes.optimize_mode import run as run_optimization
|
||||
|
||||
# debug flag
|
||||
config['app']['debug_mode'] = debug
|
||||
process_manager.add_task(
|
||||
run_optimization,
|
||||
'optimize-' + str(request_json.id),
|
||||
request_json.debug_mode,
|
||||
request_json.config,
|
||||
request_json.routes,
|
||||
request_json.extra_routes,
|
||||
request_json.start_date,
|
||||
request_json.finish_date,
|
||||
request_json.optimal_total,
|
||||
request_json.export_csv,
|
||||
request_json.export_json
|
||||
)
|
||||
|
||||
from jesse.modes.optimize_mode import optimize_mode
|
||||
# optimize_mode(start_date, finish_date, optimal_total, cpu, csv, json)
|
||||
|
||||
optimize_mode(start_date, finish_date, optimal_total, cpu, csv, json)
|
||||
return JSONResponse({'message': 'Started optimization...'}, status_code=202)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument('name', required=True, type=str)
|
||||
def make_strategy(name: str) -> None:
|
||||
@fastapi_app.delete("/optimization")
|
||||
def cancel_optimization(request_json: CancelRequestJson, authorization: Optional[str] = Header(None)):
|
||||
if not authenticator.is_valid_token(authorization):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
from jesse.services.multiprocessing import process_manager
|
||||
|
||||
process_manager.cancel_process('optimize-' + request_json.id)
|
||||
|
||||
return JSONResponse({'message': f'Optimization process with ID of {request_json.id} was requested for termination'}, status_code=202)
|
||||
|
||||
|
||||
@fastapi_app.get("/download/{mode}/{file_type}/{session_id}")
|
||||
def download(mode: str, file_type: str, session_id: str, token: str = Query(...)):
|
||||
"""
|
||||
generates a new strategy folder from jesse/strategies/ExampleStrategy
|
||||
Log files require session_id because there is one log per each session. Except for the optimize mode
|
||||
"""
|
||||
validate_cwd()
|
||||
from jesse.config import config
|
||||
if not authenticator.is_valid_token(token):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
config['app']['trading_mode'] = 'make-strategy'
|
||||
from jesse.modes import data_provider
|
||||
|
||||
register_custom_exception_handler()
|
||||
|
||||
from jesse.services import strategy_maker
|
||||
|
||||
strategy_maker.generate(name)
|
||||
return data_provider.download_file(mode, file_type, session_id)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument('name', required=True, type=str)
|
||||
def make_project(name: str) -> None:
|
||||
@fastapi_app.get("/download/optimize/log")
|
||||
def download_optimization_log(token: str = Query(...)):
|
||||
"""
|
||||
generates a new strategy folder from jesse/strategies/ExampleStrategy
|
||||
Optimization logs don't have have session ID
|
||||
"""
|
||||
from jesse.config import config
|
||||
if not authenticator.is_valid_token(token):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
config['app']['trading_mode'] = 'make-project'
|
||||
from jesse.modes import data_provider
|
||||
|
||||
register_custom_exception_handler()
|
||||
|
||||
from jesse.services import project_maker
|
||||
|
||||
project_maker.generate(name)
|
||||
return data_provider.download_file('optimize', 'log')
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option('--dna/--no-dna', default=False,
|
||||
help='Translates DNA into parameters. Used in optimize mode only')
|
||||
def routes(dna: bool) -> None:
|
||||
"""
|
||||
lists all routes
|
||||
"""
|
||||
validate_cwd()
|
||||
from jesse.config import config
|
||||
@fastapi_app.delete("/backtest")
|
||||
def cancel_backtest(request_json: CancelRequestJson, authorization: Optional[str] = Header(None)):
|
||||
if not authenticator.is_valid_token(authorization):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
config['app']['trading_mode'] = 'routes'
|
||||
from jesse.services.multiprocessing import process_manager
|
||||
|
||||
register_custom_exception_handler()
|
||||
process_manager.cancel_process('backtest-' + request_json.id)
|
||||
|
||||
from jesse.modes import routes_mode
|
||||
|
||||
routes_mode.run(dna)
|
||||
return JSONResponse({'message': f'Backtest process with ID of {request_json.id} was requested for termination'}, status_code=202)
|
||||
|
||||
|
||||
if 'plugins' in ls:
|
||||
@cli.command()
|
||||
def collect() -> None:
|
||||
"""
|
||||
fetches streamed market data such as tickers, trades, and orderbook from
|
||||
the WS connection and stores them into the database for later research.
|
||||
"""
|
||||
@fastapi_app.on_event("shutdown")
|
||||
def shutdown_event():
|
||||
from jesse.services.db import database
|
||||
database.close_connection()
|
||||
|
||||
|
||||
if HAS_LIVE_TRADE_PLUGIN:
|
||||
from jesse.services.web import fastapi_app, LiveRequestJson, LiveCancelRequestJson, GetCandlesRequestJson, \
|
||||
GetLogsRequestJson, GetOrdersRequestJson
|
||||
from jesse.services import auth as authenticator
|
||||
|
||||
@fastapi_app.post("/live")
|
||||
def live(request_json: LiveRequestJson, authorization: Optional[str] = Header(None)) -> JSONResponse:
|
||||
if not authenticator.is_valid_token(authorization):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
from jesse import validate_cwd
|
||||
|
||||
# dev_mode is used only by developers so it doesn't have to be a supported parameter
|
||||
dev_mode: bool = False
|
||||
|
||||
validate_cwd()
|
||||
|
||||
# set trading mode
|
||||
from jesse.config import config
|
||||
config['app']['trading_mode'] = 'collect'
|
||||
|
||||
register_custom_exception_handler()
|
||||
|
||||
from plugins.live.collect_mode import run
|
||||
|
||||
run()
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option('--testdrive/--no-testdrive', default=False)
|
||||
@click.option('--debug/--no-debug', default=False)
|
||||
@click.option('--dev/--no-dev', default=False)
|
||||
@click.option('--fee/--no-fee', default=True)
|
||||
def live(testdrive: bool, debug: bool, dev: bool, fee: bool) -> None:
|
||||
"""
|
||||
trades in real-time on exchange with REAL money
|
||||
"""
|
||||
validate_cwd()
|
||||
|
||||
# set trading mode
|
||||
from jesse.config import config
|
||||
config['app']['trading_mode'] = 'livetrade'
|
||||
config['app']['is_test_driving'] = testdrive
|
||||
|
||||
register_custom_exception_handler()
|
||||
|
||||
# debug flag
|
||||
config['app']['debug_mode'] = debug
|
||||
|
||||
from plugins.live import init
|
||||
from jesse.services.selectors import get_exchange
|
||||
|
||||
# fee flag
|
||||
if not fee:
|
||||
for e in config['app']['trading_exchanges']:
|
||||
config['env']['exchanges'][e]['fee'] = 0
|
||||
get_exchange(e).fee = 0
|
||||
|
||||
# inject live config
|
||||
init(config)
|
||||
|
||||
# execute live session
|
||||
from plugins.live.live_mode import run
|
||||
run(dev)
|
||||
from jesse_live import live_mode
|
||||
from jesse.services.multiprocessing import process_manager
|
||||
|
||||
trading_mode = 'livetrade' if request_json.paper_mode is False else 'papertrade'
|
||||
|
||||
process_manager.add_task(
|
||||
live_mode.run,
|
||||
f'{trading_mode}-' + str(request_json.id),
|
||||
request_json.debug_mode,
|
||||
dev_mode,
|
||||
request_json.config,
|
||||
request_json.routes,
|
||||
request_json.extra_routes,
|
||||
trading_mode,
|
||||
)
|
||||
|
||||
mode = 'live' if request_json.paper_mode is False else 'paper'
|
||||
|
||||
return JSONResponse({'message': f"Started {mode} trading..."}, status_code=202)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option('--debug/--no-debug', default=False)
|
||||
@click.option('--dev/--no-dev', default=False)
|
||||
@click.option('--fee/--no-fee', default=True)
|
||||
def paper(debug: bool, dev: bool, fee: bool) -> None:
|
||||
"""
|
||||
trades in real-time on exchange with PAPER money
|
||||
"""
|
||||
@fastapi_app.delete("/live")
|
||||
def cancel_backtest(request_json: LiveCancelRequestJson, authorization: Optional[str] = Header(None)):
|
||||
if not authenticator.is_valid_token(authorization):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
from jesse.services.multiprocessing import process_manager
|
||||
|
||||
trading_mode = 'livetrade' if request_json.paper_mode is False else 'papertrade'
|
||||
|
||||
process_manager.cancel_process(f'{trading_mode}-' + request_json.id)
|
||||
|
||||
return JSONResponse({'message': f'Live process with ID of {request_json.id} terminated.'}, status_code=200)
|
||||
|
||||
|
||||
@fastapi_app.post('/get-candles')
|
||||
def get_candles(json_request: GetCandlesRequestJson, authorization: Optional[str] = Header(None)) -> JSONResponse:
|
||||
if not authenticator.is_valid_token(authorization):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
from jesse import validate_cwd
|
||||
|
||||
validate_cwd()
|
||||
|
||||
# set trading mode
|
||||
from jesse.config import config
|
||||
config['app']['trading_mode'] = 'papertrade'
|
||||
from jesse.modes.data_provider import get_candles as gc
|
||||
|
||||
register_custom_exception_handler()
|
||||
arr = gc(json_request.exchange, json_request.symbol, json_request.timeframe)
|
||||
|
||||
# debug flag
|
||||
config['app']['debug_mode'] = debug
|
||||
return JSONResponse({
|
||||
'id': json_request.id,
|
||||
'data': arr
|
||||
}, status_code=200)
|
||||
|
||||
from plugins.live import init
|
||||
from jesse.services.selectors import get_exchange
|
||||
|
||||
# fee flag
|
||||
if not fee:
|
||||
for e in config['app']['trading_exchanges']:
|
||||
config['env']['exchanges'][e]['fee'] = 0
|
||||
get_exchange(e).fee = 0
|
||||
@fastapi_app.post('/get-logs')
|
||||
def get_logs(json_request: GetLogsRequestJson, authorization: Optional[str] = Header(None)) -> JSONResponse:
|
||||
if not authenticator.is_valid_token(authorization):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
# inject live config
|
||||
init(config)
|
||||
from jesse_live.services.data_provider import get_logs as gl
|
||||
|
||||
# execute live session
|
||||
from plugins.live.live_mode import run
|
||||
run(dev)
|
||||
arr = gl(json_request.session_id, json_request.type)
|
||||
|
||||
return JSONResponse({
|
||||
'id': json_request.id,
|
||||
'data': arr
|
||||
}, status_code=200)
|
||||
|
||||
|
||||
@fastapi_app.post('/get-orders')
|
||||
def get_orders(json_request: GetOrdersRequestJson, authorization: Optional[str] = Header(None)) -> JSONResponse:
|
||||
if not authenticator.is_valid_token(authorization):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
from jesse_live.services.data_provider import get_orders as go
|
||||
|
||||
arr = go(json_request.session_id)
|
||||
|
||||
return JSONResponse({
|
||||
'id': json_request.id,
|
||||
'data': arr
|
||||
}, status_code=200)
|
||||
|
||||
|
||||
# Mount static files.Must be loaded at the end to prevent overlapping with API endpoints
|
||||
fastapi_app.mount("/", StaticFiles(directory=f"{JESSE_DIR}/static"), name="static")
|
||||
|
||||
167
jesse/config.py
167
jesse/config.py
@@ -1,14 +1,9 @@
|
||||
import jesse.helpers as jh
|
||||
|
||||
|
||||
config = {
|
||||
# these values are related to the user's environment
|
||||
'env': {
|
||||
'databases': {
|
||||
'postgres_host': '127.0.0.1',
|
||||
'postgres_name': 'jesse_db',
|
||||
'postgres_port': 5432,
|
||||
'postgres_username': 'jesse_user',
|
||||
'postgres_password': 'password',
|
||||
},
|
||||
|
||||
'caching': {
|
||||
'driver': 'pickle'
|
||||
},
|
||||
@@ -42,6 +37,64 @@ config = {
|
||||
],
|
||||
},
|
||||
|
||||
'Bybit Perpetual': {
|
||||
'fee': 0.00075,
|
||||
|
||||
# backtest mode only: accepted are 'spot' and 'futures'
|
||||
# 'spot' support is currently very limited - you can use 'futures' with leverage 1 for now
|
||||
'type': 'futures',
|
||||
|
||||
# futures mode only
|
||||
'settlement_currency': 'USDT',
|
||||
# accepted values are: 'cross' and 'isolated'
|
||||
'futures_leverage_mode': 'cross',
|
||||
# 1x, 2x, 10x, 50x, etc. Enter as integers
|
||||
'futures_leverage': 1,
|
||||
|
||||
'assets': [
|
||||
{'asset': 'USDT', 'balance': 10_000},
|
||||
],
|
||||
},
|
||||
|
||||
'Testnet Bybit Perpetual': {
|
||||
'fee': 0.00075,
|
||||
|
||||
# backtest mode only: accepted are 'spot' and 'futures'
|
||||
# 'spot' support is currently very limited - you can use 'futures' with leverage 1 for now
|
||||
'type': 'futures',
|
||||
|
||||
# futures mode only
|
||||
'settlement_currency': 'USDT',
|
||||
# accepted values are: 'cross' and 'isolated'
|
||||
'futures_leverage_mode': 'cross',
|
||||
# 1x, 2x, 10x, 50x, etc. Enter as integers
|
||||
'futures_leverage': 1,
|
||||
|
||||
'assets': [
|
||||
{'asset': 'USDT', 'balance': 10_000},
|
||||
],
|
||||
},
|
||||
|
||||
# https://ftx.com/markets/future
|
||||
'FTX Futures': {
|
||||
'fee': 0.0006,
|
||||
|
||||
# backtest mode only: accepted are 'spot' and 'futures'
|
||||
# 'spot' support is currently very limited - you can use 'futures' with leverage 1 for now
|
||||
'type': 'futures',
|
||||
|
||||
# futures mode only
|
||||
'settlement_currency': 'USD',
|
||||
# accepted values are: 'cross' and 'isolated'
|
||||
'futures_leverage_mode': 'cross',
|
||||
# 1x, 2x, 10x, 20x, etc. Enter as integers
|
||||
'futures_leverage': 1,
|
||||
|
||||
'assets': [
|
||||
{'asset': 'USD', 'balance': 10_000},
|
||||
],
|
||||
},
|
||||
|
||||
# https://www.bitfinex.com
|
||||
'Bitfinex': {
|
||||
'fee': 0.002,
|
||||
@@ -56,7 +109,6 @@ config = {
|
||||
# 1x, 2x, 10x, 50x, etc. Enter as integers
|
||||
'futures_leverage': 1,
|
||||
|
||||
# used for spot exchange only
|
||||
'assets': [
|
||||
{'asset': 'USDT', 'balance': 10_000},
|
||||
{'asset': 'USD', 'balance': 10_000},
|
||||
@@ -78,7 +130,6 @@ config = {
|
||||
# 1x, 2x, 10x, 50x, etc. Enter as integers
|
||||
'futures_leverage': 1,
|
||||
|
||||
# used for spot exchange only
|
||||
'assets': [
|
||||
{'asset': 'USDT', 'balance': 10_000},
|
||||
{'asset': 'BTC', 'balance': 0},
|
||||
@@ -99,7 +150,6 @@ config = {
|
||||
# 1x, 2x, 10x, 50x, etc. Enter as integers
|
||||
'futures_leverage': 1,
|
||||
|
||||
# used for spot exchange only
|
||||
'assets': [
|
||||
{'asset': 'USDT', 'balance': 10_000},
|
||||
],
|
||||
@@ -119,7 +169,6 @@ config = {
|
||||
# 1x, 2x, 10x, 50x, etc. Enter as integers
|
||||
'futures_leverage': 1,
|
||||
|
||||
# used for spot mode
|
||||
'assets': [
|
||||
{'asset': 'USDT', 'balance': 10_000},
|
||||
],
|
||||
@@ -139,7 +188,6 @@ config = {
|
||||
# 1x, 2x, 10x, 50x, etc. Enter as integers
|
||||
'futures_leverage': 1,
|
||||
|
||||
# used for spot exchange only
|
||||
'assets': [
|
||||
{'asset': 'USDT', 'balance': 10_000},
|
||||
{'asset': 'USD', 'balance': 10_000},
|
||||
@@ -148,20 +196,6 @@ config = {
|
||||
},
|
||||
},
|
||||
|
||||
# changes the metrics output of the backtest
|
||||
'metrics': {
|
||||
'sharpe_ratio': True,
|
||||
'calmar_ratio': False,
|
||||
'sortino_ratio': False,
|
||||
'omega_ratio': False,
|
||||
'winning_streak': False,
|
||||
'losing_streak': False,
|
||||
'largest_losing_trade': False,
|
||||
'largest_winning_trade': False,
|
||||
'total_winning_trades': False,
|
||||
'total_losing_trades': False,
|
||||
},
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# Optimize mode
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
@@ -169,7 +203,7 @@ config = {
|
||||
# Below configurations are related to the optimize mode
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
'optimization': {
|
||||
# sharpe, calmar, sortino, omega
|
||||
# sharpe, calmar, sortino, omega, serenity, smart sharpe, smart sortino
|
||||
'ratio': 'sharpe',
|
||||
},
|
||||
|
||||
@@ -215,30 +249,73 @@ config = {
|
||||
|
||||
# this would enable many console.log()s in the code, which are helpful for debugging.
|
||||
'debug_mode': False,
|
||||
|
||||
# this is only used for the live unit tests
|
||||
'is_unit_testing': False,
|
||||
},
|
||||
}
|
||||
|
||||
backup_config = config.copy()
|
||||
|
||||
|
||||
def set_config(c) -> None:
|
||||
def set_config(conf: dict) -> None:
|
||||
global config
|
||||
config['env'] = c
|
||||
# add sandbox because it isn't in the local config file
|
||||
config['env']['exchanges']['Sandbox'] = {
|
||||
'type': 'spot',
|
||||
# used only in futures trading
|
||||
'settlement_currency': 'USDT',
|
||||
'fee': 0,
|
||||
'futures_leverage_mode': 'cross',
|
||||
'futures_leverage': 1,
|
||||
'assets': [
|
||||
{'asset': 'USDT', 'balance': 10_000},
|
||||
{'asset': 'BTC', 'balance': 0},
|
||||
],
|
||||
}
|
||||
|
||||
# optimization mode only
|
||||
if jh.is_optimizing():
|
||||
# ratio
|
||||
config['env']['optimization']['ratio'] = conf['ratio']
|
||||
# exchange info (only one because the optimize mode supports only one trading route at the moment)
|
||||
config['env']['optimization']['exchange'] = conf['exchange']
|
||||
# warm_up_candles
|
||||
config['env']['optimization']['warmup_candles_num'] = int(conf['warm_up_candles'])
|
||||
|
||||
# backtest and live
|
||||
if jh.is_backtesting() or jh.is_live():
|
||||
# warm_up_candles
|
||||
config['env']['data']['warmup_candles_num'] = int(conf['warm_up_candles'])
|
||||
# logs
|
||||
config['env']['logging'] = conf['logging']
|
||||
# exchanges
|
||||
for key, e in conf['exchanges'].items():
|
||||
config['env']['exchanges'][e['name']] = {
|
||||
'fee': float(e['fee']),
|
||||
'type': 'futures',
|
||||
# used only in futures trading
|
||||
# 'settlement_currency': 'USDT',
|
||||
'settlement_currency': jh.get_settlement_currency_from_exchange(e['name']),
|
||||
# accepted values are: 'cross' and 'isolated'
|
||||
'futures_leverage_mode': e['futures_leverage_mode'],
|
||||
# 1x, 2x, 10x, 50x, etc. Enter as integers
|
||||
'futures_leverage': int(e['futures_leverage']),
|
||||
'assets': [
|
||||
{'asset': 'USDT', 'balance': float(e['balance'])},
|
||||
],
|
||||
}
|
||||
|
||||
# live mode only
|
||||
if jh.is_live():
|
||||
config['env']['notifications'] = conf['notifications']
|
||||
|
||||
# TODO: must become a config value later when we go after multi account support?
|
||||
config['env']['identifier'] = 'main'
|
||||
|
||||
# # add sandbox because it isn't in the local config file but it is needed since we might have replaced it
|
||||
# config['env']['exchanges']['Sandbox'] = {
|
||||
# 'type': 'spot',
|
||||
# # used only in futures trading
|
||||
# 'settlement_currency': 'USDT',
|
||||
# 'fee': 0,
|
||||
# 'futures_leverage_mode': 'cross',
|
||||
# 'futures_leverage': 1,
|
||||
# 'assets': [
|
||||
# {'asset': 'USDT', 'balance': 10_000},
|
||||
# {'asset': 'BTC', 'balance': 0},
|
||||
# ],
|
||||
# }
|
||||
|
||||
|
||||
def reset_config() -> None:
|
||||
global config
|
||||
config = backup_config.copy()
|
||||
|
||||
|
||||
backup_config = config.copy()
|
||||
|
||||
@@ -12,7 +12,9 @@ class order_statuses:
|
||||
ACTIVE = 'ACTIVE'
|
||||
CANCELED = 'CANCELED'
|
||||
EXECUTED = 'EXECUTED'
|
||||
PARTIALLY_EXECUTED = 'PARTIALLY EXECUTED'
|
||||
QUEUED = 'QUEUED'
|
||||
LIQUIDATED = 'LIQUIDATED'
|
||||
|
||||
|
||||
class timeframes:
|
||||
@@ -30,8 +32,6 @@ class timeframes:
|
||||
HOUR_8 = '8h'
|
||||
HOUR_12 = '12h'
|
||||
DAY_1 = '1D'
|
||||
DAY_3 = '3D'
|
||||
WEEK_1 = '1W'
|
||||
|
||||
|
||||
class colors:
|
||||
|
||||
@@ -18,10 +18,6 @@ class InvalidStrategy(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Breaker(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class CandleNotFoundInDatabase(Exception):
|
||||
pass
|
||||
|
||||
@@ -34,10 +30,6 @@ class SymbolNotFound(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MaximumDecimal(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class RouteNotFound(Exception):
|
||||
pass
|
||||
|
||||
@@ -54,6 +46,10 @@ class ExchangeNotResponding(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ExchangeRejectedOrder(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidShape(Exception):
|
||||
pass
|
||||
|
||||
@@ -72,3 +68,7 @@ class NegativeBalance(Exception):
|
||||
|
||||
class InsufficientMargin(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Termination(Exception):
|
||||
pass
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from typing import Union
|
||||
from jesse.models import Order
|
||||
|
||||
class Exchange(ABC):
|
||||
"""
|
||||
@@ -7,25 +8,29 @@ class Exchange(ABC):
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def market_order(self, symbol, qty, current_price, side, role, flags):
|
||||
def market_order(self, symbol: str, qty: float, current_price: float, side: str, role: str, flags: list) -> Order:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def limit_order(self, symbol, qty, price, side, role, flags):
|
||||
def limit_order(self, symbol: str, qty: float, price: float, side: str, role: str, flags: list) -> Order:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def stop_order(self, symbol, qty, price, side, role, flags):
|
||||
def stop_order(self, symbol: str, qty: float, price: float, side: str, role: str, flags: list) -> Order:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def cancel_all_orders(self, symbol):
|
||||
def cancel_all_orders(self, symbol: str) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def cancel_order(self, symbol, order_id):
|
||||
def cancel_order(self, symbol: str, order_id: str) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_exec_inst(self, flags):
|
||||
def get_exec_inst(self, flags: list) -> Union[str, None]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _fetch_precisions(self) -> None:
|
||||
pass
|
||||
|
||||
@@ -3,27 +3,14 @@ from jesse.enums import order_types
|
||||
from jesse.exchanges.exchange import Exchange
|
||||
from jesse.models import Order
|
||||
from jesse.store import store
|
||||
|
||||
from typing import Union
|
||||
|
||||
class Sandbox(Exchange):
|
||||
"""
|
||||
|
||||
"""
|
||||
def __init__(self, name='Sandbox'):
|
||||
super().__init__()
|
||||
self.name = name
|
||||
|
||||
def market_order(self, symbol, qty, current_price, side, role, flags):
|
||||
"""
|
||||
|
||||
:param symbol:
|
||||
:param qty:
|
||||
:param current_price:
|
||||
:param side:
|
||||
:param role:
|
||||
:param flags:
|
||||
:return:
|
||||
"""
|
||||
def market_order(self, symbol: str, qty: float, current_price: float, side: str, role: str, flags: list) -> Order:
|
||||
order = Order({
|
||||
'id': jh.generate_unique_id(),
|
||||
'symbol': symbol,
|
||||
@@ -42,17 +29,7 @@ class Sandbox(Exchange):
|
||||
|
||||
return order
|
||||
|
||||
def limit_order(self, symbol, qty, price, side, role, flags):
|
||||
"""
|
||||
|
||||
:param symbol:
|
||||
:param qty:
|
||||
:param price:
|
||||
:param side:
|
||||
:param role:
|
||||
:param flags:
|
||||
:return:
|
||||
"""
|
||||
def limit_order(self, symbol: str, qty: float, price: float, side: str, role: str, flags: list) -> Order:
|
||||
order = Order({
|
||||
'id': jh.generate_unique_id(),
|
||||
'symbol': symbol,
|
||||
@@ -69,17 +46,7 @@ class Sandbox(Exchange):
|
||||
|
||||
return order
|
||||
|
||||
def stop_order(self, symbol, qty, price, side, role, flags):
|
||||
"""
|
||||
|
||||
:param symbol:
|
||||
:param qty:
|
||||
:param price:
|
||||
:param side:
|
||||
:param role:
|
||||
:param flags:
|
||||
:return:
|
||||
"""
|
||||
def stop_order(self, symbol: str, qty: float, price: float, side: str, role: str, flags: list) -> Order:
|
||||
order = Order({
|
||||
'id': jh.generate_unique_id(),
|
||||
'symbol': symbol,
|
||||
@@ -96,11 +63,7 @@ class Sandbox(Exchange):
|
||||
|
||||
return order
|
||||
|
||||
def cancel_all_orders(self, symbol):
|
||||
"""
|
||||
|
||||
:param symbol:
|
||||
"""
|
||||
def cancel_all_orders(self, symbol: str) -> None:
|
||||
orders = filter(lambda o: o.is_new,
|
||||
store.orders.get_orders(self.name, symbol))
|
||||
|
||||
@@ -108,22 +71,15 @@ class Sandbox(Exchange):
|
||||
o.cancel()
|
||||
|
||||
if not jh.is_unit_testing():
|
||||
store.orders.storage['{}-{}'.format(self.name, symbol)].clear()
|
||||
store.orders.storage[f'{self.name}-{symbol}'].clear()
|
||||
|
||||
def cancel_order(self, symbol, order_id):
|
||||
"""
|
||||
|
||||
:param symbol:
|
||||
:param order_id:
|
||||
"""
|
||||
def cancel_order(self, symbol: str, order_id: str) -> None:
|
||||
store.orders.get_order_by_id(self.name, symbol, order_id).cancel()
|
||||
|
||||
def get_exec_inst(self, flags):
|
||||
"""
|
||||
|
||||
:param flags:
|
||||
:return:
|
||||
"""
|
||||
def get_exec_inst(self, flags: list) -> Union[str, None]:
|
||||
if flags:
|
||||
return flags[0]
|
||||
return None
|
||||
|
||||
def _fetch_precisions(self) -> None:
|
||||
pass
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from .candle_factory import fake_candle
|
||||
from .candle_factory import fake_range_candle
|
||||
from .candle_factory import fake_range_candle_from_range_prices
|
||||
from .candle_factory import range_candles
|
||||
from .candle_factory import candles_from_close_prices
|
||||
from .order_factory import fake_order
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from random import randint
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
|
||||
first_timestamp = 1552309186171
|
||||
# 2021-01-01T00:00:00+00:00
|
||||
first_timestamp = 1609459080000
|
||||
open_price = randint(40, 100)
|
||||
close_price = randint(open_price, 110) if randint(0, 1) else randint(
|
||||
30, open_price)
|
||||
@@ -12,11 +14,9 @@ min_price = min(open_price, close_price)
|
||||
low_price = min_price if randint(0, 1) else randint(min_price, min_price + 10)
|
||||
|
||||
|
||||
def fake_range_candle(count) -> np.ndarray:
|
||||
def range_candles(count: int) -> np.ndarray:
|
||||
"""
|
||||
|
||||
:param count:
|
||||
:return:
|
||||
Generates a range of candles with random values.
|
||||
"""
|
||||
fake_candle(reset=True)
|
||||
arr = np.zeros((count, 6))
|
||||
@@ -25,11 +25,10 @@ def fake_range_candle(count) -> np.ndarray:
|
||||
return arr
|
||||
|
||||
|
||||
def fake_range_candle_from_range_prices(prices) -> np.ndarray:
|
||||
def candles_from_close_prices(prices: Union[list, range]) -> np.ndarray:
|
||||
"""
|
||||
|
||||
:param prices:
|
||||
:return:
|
||||
Generates a range of candles from a list of close prices.
|
||||
The first candle has the timestamp of "2021-01-01T00:00:00+00:00"
|
||||
"""
|
||||
fake_candle(reset=True)
|
||||
global first_timestamp
|
||||
@@ -55,13 +54,7 @@ def fake_range_candle_from_range_prices(prices) -> np.ndarray:
|
||||
return np.array(arr)
|
||||
|
||||
|
||||
def fake_candle(attributes=None, reset=False):
|
||||
"""
|
||||
|
||||
:param attributes:
|
||||
:param reset:
|
||||
:return:
|
||||
"""
|
||||
def fake_candle(attributes: dict = None, reset: bool = False) -> np.ndarray:
|
||||
global first_timestamp
|
||||
global open_price
|
||||
global close_price
|
||||
@@ -71,7 +64,7 @@ def fake_candle(attributes=None, reset=False):
|
||||
global low_price
|
||||
|
||||
if reset:
|
||||
first_timestamp = 1552309186171
|
||||
first_timestamp = 1609459080000
|
||||
open_price = randint(40, 100)
|
||||
close_price = randint(open_price, 110)
|
||||
high_price = max(open_price, close_price)
|
||||
|
||||
@@ -7,7 +7,7 @@ from jesse.models import Order
|
||||
first_timestamp = 1552309186171
|
||||
|
||||
|
||||
def fake_order(attributes=None):
|
||||
def fake_order(attributes: dict = None) -> Order:
|
||||
"""
|
||||
|
||||
:param attributes:
|
||||
|
||||
408
jesse/helpers.py
408
jesse/helpers.py
@@ -6,7 +6,7 @@ import string
|
||||
import sys
|
||||
import uuid
|
||||
from typing import List, Tuple, Union, Any
|
||||
|
||||
from pprint import pprint
|
||||
import arrow
|
||||
import click
|
||||
import numpy as np
|
||||
@@ -77,7 +77,7 @@ def color(msg_text: str, msg_color: str) -> str:
|
||||
return click.style(msg_text, fg='magenta')
|
||||
if msg_color == 'cyan':
|
||||
return click.style(msg_text, fg='cyan')
|
||||
if msg_color in ['white', 'gray']:
|
||||
if msg_color in {'white', 'gray'}:
|
||||
return click.style(msg_text, fg='white')
|
||||
|
||||
raise ValueError('unsupported color')
|
||||
@@ -90,13 +90,11 @@ def convert_number(old_max: float, old_min: float, new_max: float, new_min: floa
|
||||
"""
|
||||
# validation
|
||||
if old_value > old_max or old_value < old_min:
|
||||
raise ValueError('old_value:{} must be within the range. {}-{}'.format(old_value, old_min, old_max))
|
||||
raise ValueError(f'old_value:{old_value} must be within the range. {old_min}-{old_max}')
|
||||
|
||||
old_range = (old_max - old_min)
|
||||
new_range = (new_max - new_min)
|
||||
new_value = (((old_value - old_min) * new_range) / old_range) + new_min
|
||||
|
||||
return new_value
|
||||
return (((old_value - old_min) * new_range) / old_range) + new_min
|
||||
|
||||
|
||||
def dashless_symbol(symbol: str) -> str:
|
||||
@@ -104,7 +102,18 @@ def dashless_symbol(symbol: str) -> str:
|
||||
|
||||
|
||||
def dashy_symbol(symbol: str) -> str:
|
||||
return symbol[0:3] + '-' + symbol[3:]
|
||||
# if already has '-' in symbol, return symbol
|
||||
if '-' in symbol:
|
||||
return symbol
|
||||
|
||||
from jesse.config import config
|
||||
|
||||
for s in config['app']['considering_symbols']:
|
||||
compare_symbol = dashless_symbol(s)
|
||||
if compare_symbol == symbol:
|
||||
return s
|
||||
|
||||
return f"{symbol[0:3]}-{symbol[3:]}"
|
||||
|
||||
|
||||
def date_diff_in_days(date1: arrow.arrow.Arrow, date2: arrow.arrow.Arrow) -> int:
|
||||
@@ -199,6 +208,16 @@ def file_exists(path: str) -> bool:
|
||||
return os.path.isfile(path)
|
||||
|
||||
|
||||
def clear_file(path: str) -> None:
|
||||
with open(path, 'w') as f:
|
||||
f.write('')
|
||||
|
||||
|
||||
def make_directory(path: str) -> None:
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
|
||||
|
||||
def floor_with_precision(num: float, precision: int = 0) -> float:
|
||||
temp = 10 ** precision
|
||||
return math.floor(num * temp) / temp
|
||||
@@ -259,9 +278,9 @@ def get_config(keys: str, default: Any = None) -> Any:
|
||||
if not str:
|
||||
raise ValueError('keys string cannot be empty')
|
||||
|
||||
if is_unit_testing() or not keys in CACHED_CONFIG:
|
||||
if os.environ.get(keys.upper().replace(".", "_")) is not None:
|
||||
CACHED_CONFIG[keys] = os.environ.get(keys.upper().replace(".", "_"))
|
||||
if is_unit_testing() or keys not in CACHED_CONFIG:
|
||||
if os.environ.get(keys.upper().replace(".", "_").replace(" ", "_")) is not None:
|
||||
CACHED_CONFIG[keys] = os.environ.get(keys.upper().replace(".", "_").replace(" ", "_"))
|
||||
else:
|
||||
from functools import reduce
|
||||
from jesse.config import config
|
||||
@@ -274,10 +293,17 @@ def get_config(keys: str, default: Any = None) -> Any:
|
||||
def get_strategy_class(strategy_name: str):
|
||||
from pydoc import locate
|
||||
|
||||
if is_unit_testing():
|
||||
return locate('jesse.strategies.{}.{}'.format(strategy_name, strategy_name))
|
||||
if not is_unit_testing():
|
||||
return locate(f'strategies.{strategy_name}.{strategy_name}')
|
||||
path = sys.path[0]
|
||||
# live plugin
|
||||
if path.endswith('jesse-live'):
|
||||
strategy_dir = f'tests.strategies.{strategy_name}.{strategy_name}'
|
||||
# main framework
|
||||
else:
|
||||
return locate('strategies.{}.{}'.format(strategy_name, strategy_name))
|
||||
strategy_dir = f'jesse.strategies.{strategy_name}.{strategy_name}'
|
||||
|
||||
return locate(strategy_dir)
|
||||
|
||||
|
||||
def insecure_hash(msg: str) -> str:
|
||||
@@ -316,7 +342,7 @@ def is_debugging() -> bool:
|
||||
|
||||
def is_importing_candles() -> bool:
|
||||
from jesse.config import config
|
||||
return config['app']['trading_mode'] == 'import-candles'
|
||||
return config['app']['trading_mode'] == 'candles'
|
||||
|
||||
|
||||
def is_live() -> bool:
|
||||
@@ -344,10 +370,12 @@ def is_test_driving() -> bool:
|
||||
|
||||
|
||||
def is_unit_testing() -> bool:
|
||||
return "pytest" in sys.modules
|
||||
from jesse.config import config
|
||||
# config['app']['is_unit_testing'] is only set in the live plugin unit tests
|
||||
return "pytest" in sys.modules or config['app']['is_unit_testing']
|
||||
|
||||
|
||||
def is_valid_uuid(uuid_to_test, version: int = 4) -> bool:
|
||||
def is_valid_uuid(uuid_to_test:str, version: int = 4) -> bool:
|
||||
try:
|
||||
uuid_obj = uuid.UUID(uuid_to_test, version=version)
|
||||
except ValueError:
|
||||
@@ -357,18 +385,14 @@ def is_valid_uuid(uuid_to_test, version: int = 4) -> bool:
|
||||
|
||||
def key(exchange: str, symbol: str, timeframe: str = None):
|
||||
if timeframe is None:
|
||||
return '{}-{}'.format(exchange, symbol)
|
||||
return f'{exchange}-{symbol}'
|
||||
|
||||
return '{}-{}-{}'.format(exchange, symbol, timeframe)
|
||||
return f'{exchange}-{symbol}-{timeframe}'
|
||||
|
||||
|
||||
def max_timeframe(timeframes_list: list) -> str:
|
||||
from jesse.enums import timeframes
|
||||
|
||||
if timeframes.WEEK_1 in timeframes_list:
|
||||
return timeframes.WEEK_1
|
||||
if timeframes.DAY_3 in timeframes_list:
|
||||
return timeframes.DAY_3
|
||||
if timeframes.DAY_1 in timeframes_list:
|
||||
return timeframes.DAY_1
|
||||
if timeframes.HOUR_12 in timeframes_list:
|
||||
@@ -403,29 +427,42 @@ def normalize(x: float, x_min: float, x_max: float) -> float:
|
||||
"""
|
||||
Rescaling data to have values between 0 and 1
|
||||
"""
|
||||
x_new = (x - x_min) / (x_max - x_min)
|
||||
return x_new
|
||||
return (x - x_min) / (x_max - x_min)
|
||||
|
||||
|
||||
def now() -> int:
|
||||
return now_to_timestamp()
|
||||
def now(force_fresh=False) -> int:
|
||||
"""
|
||||
Always returns the current time in milliseconds but rounds time in matter of seconds
|
||||
"""
|
||||
return now_to_timestamp(force_fresh)
|
||||
|
||||
|
||||
def now_to_timestamp() -> int:
|
||||
if not (is_live() or is_collecting_data() or is_importing_candles()):
|
||||
def now_to_timestamp(force_fresh=False) -> int:
|
||||
if not force_fresh and (not (is_live() or is_collecting_data() or is_importing_candles())):
|
||||
from jesse.store import store
|
||||
return store.app.time
|
||||
|
||||
return arrow.utcnow().int_timestamp * 1000
|
||||
|
||||
|
||||
def current_1m_candle_timestamp():
|
||||
return arrow.utcnow().floor('minute').int_timestamp * 1000
|
||||
|
||||
|
||||
def np_ffill(arr: np.ndarray, axis: int = 0) -> np.ndarray:
|
||||
idx_shape = tuple([slice(None)] + [np.newaxis] * (len(arr.shape) - axis - 1))
|
||||
idx = np.where(~np.isnan(arr), np.arange(arr.shape[axis])[idx_shape], 0)
|
||||
np.maximum.accumulate(idx, axis=axis, out=idx)
|
||||
slc = [np.arange(k)[tuple([slice(None) if dim == i else np.newaxis
|
||||
for dim in range(len(arr.shape))])]
|
||||
for i, k in enumerate(arr.shape)]
|
||||
slc = [
|
||||
np.arange(k)[
|
||||
tuple(
|
||||
slice(None) if dim == i else np.newaxis
|
||||
for dim in range(len(arr.shape))
|
||||
)
|
||||
]
|
||||
for i, k in enumerate(arr.shape)
|
||||
]
|
||||
|
||||
slc[axis] = idx
|
||||
return arr[tuple(slc)]
|
||||
|
||||
@@ -450,9 +487,10 @@ def opposite_side(s: str) -> str:
|
||||
|
||||
if s == sides.BUY:
|
||||
return sides.SELL
|
||||
if s == sides.SELL:
|
||||
elif s == sides.SELL:
|
||||
return sides.BUY
|
||||
raise ValueError('unsupported side')
|
||||
else:
|
||||
raise ValueError(f'{s} is not a valid input for side')
|
||||
|
||||
|
||||
def opposite_type(t: str) -> str:
|
||||
@@ -470,10 +508,10 @@ def orderbook_insertion_index_search(arr, target: int, ascending: bool = True) -
|
||||
lower = 0
|
||||
upper = len(arr)
|
||||
|
||||
if ascending:
|
||||
while lower < upper:
|
||||
x = lower + (upper - lower) // 2
|
||||
val = arr[x][0]
|
||||
while lower < upper:
|
||||
x = lower + (upper - lower) // 2
|
||||
val = arr[x][0]
|
||||
if ascending:
|
||||
if target == val:
|
||||
return True, x
|
||||
elif target > val:
|
||||
@@ -484,20 +522,16 @@ def orderbook_insertion_index_search(arr, target: int, ascending: bool = True) -
|
||||
if lower == x:
|
||||
return False, lower
|
||||
upper = x
|
||||
else:
|
||||
while lower < upper:
|
||||
x = lower + (upper - lower) // 2
|
||||
val = arr[x][0]
|
||||
if target == val:
|
||||
return True, x
|
||||
elif target < val:
|
||||
if lower == x:
|
||||
return False, lower + 1
|
||||
lower = x
|
||||
elif target > val:
|
||||
if lower == x:
|
||||
return False, lower
|
||||
upper = x
|
||||
elif target == val:
|
||||
return True, x
|
||||
elif target < val:
|
||||
if lower == x:
|
||||
return False, lower + 1
|
||||
lower = x
|
||||
elif target > val:
|
||||
if lower == x:
|
||||
return False, lower
|
||||
upper = x
|
||||
|
||||
|
||||
def orderbook_trim_price(p: float, ascending: bool, unit: float) -> float:
|
||||
@@ -516,15 +550,14 @@ def orderbook_trim_price(p: float, ascending: bool, unit: float) -> float:
|
||||
def prepare_qty(qty: float, side: str) -> float:
|
||||
if side.lower() in ('sell', 'short'):
|
||||
return -abs(qty)
|
||||
|
||||
if side.lower() in ('buy', 'long'):
|
||||
elif side.lower() in ('buy', 'long'):
|
||||
return abs(qty)
|
||||
|
||||
raise TypeError()
|
||||
else:
|
||||
raise ValueError(f'{side} is not a valid input')
|
||||
|
||||
|
||||
def python_version() -> float:
|
||||
return float('{}.{}'.format(sys.version_info[0], sys.version_info[1]))
|
||||
def python_version() -> tuple:
|
||||
return sys.version_info[:2]
|
||||
|
||||
|
||||
def quote_asset(symbol: str) -> str:
|
||||
@@ -532,11 +565,11 @@ def quote_asset(symbol: str) -> str:
|
||||
return symbol.split('-')[1]
|
||||
except IndexError:
|
||||
from jesse.exceptions import InvalidRoutes
|
||||
raise InvalidRoutes("The symbol format is incorrect. Correct example: 'BTC-USDT'. Yours is '{}'".format(symbol))
|
||||
raise InvalidRoutes(f"The symbol format is incorrect. Correct example: 'BTC-USDT'. Yours is '{symbol}'")
|
||||
|
||||
|
||||
def random_str(num_characters: int = 8) -> str:
|
||||
return ''.join(random.choice(string.ascii_letters) for i in range(num_characters))
|
||||
return ''.join(random.choice(string.ascii_letters) for _ in range(num_characters))
|
||||
|
||||
|
||||
def readable_duration(seconds: int, granularity: int = 2) -> str:
|
||||
@@ -557,7 +590,7 @@ def readable_duration(seconds: int, granularity: int = 2) -> str:
|
||||
seconds -= value * count
|
||||
if value == 1:
|
||||
name = name.rstrip('s')
|
||||
result.append("{} {}".format(value, name))
|
||||
result.append(f"{value} {name}")
|
||||
return ', '.join(result[:granularity])
|
||||
|
||||
|
||||
@@ -565,51 +598,54 @@ def relative_to_absolute(path: str) -> str:
|
||||
return os.path.abspath(path)
|
||||
|
||||
|
||||
def round_price_for_live_mode(price: float, roundable_price: float) -> Union[float, np.array]:
|
||||
def round_price_for_live_mode(price, precision: int) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
Rounds price(s) based on exchange requirements
|
||||
|
||||
:param price: float
|
||||
:param roundable_price: float
|
||||
:param precision: int
|
||||
:return: float | nd.array
|
||||
"""
|
||||
n = int(math.log10(price))
|
||||
|
||||
if price < 1:
|
||||
price_round_precision = abs(n - 4)
|
||||
else:
|
||||
price_round_precision = 3 - n
|
||||
if price_round_precision < 0:
|
||||
price_round_precision = 0
|
||||
|
||||
return np.round(roundable_price, price_round_precision)
|
||||
return np.round(price, precision)
|
||||
|
||||
|
||||
def round_qty_for_live_mode(price: float, roundable_qty: float) -> Union[float, np.array]:
|
||||
def round_qty_for_live_mode(roundable_qty: float, precision: int) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
Rounds qty(s) based on exchange requirements
|
||||
|
||||
:param price: float
|
||||
:param roundable_qty: float | nd.array
|
||||
:param precision: int
|
||||
:return: float | nd.array
|
||||
"""
|
||||
n = int(math.log10(price))
|
||||
|
||||
if price < 1:
|
||||
qty_round_precision = 0
|
||||
else:
|
||||
qty_round_precision = n + 1
|
||||
if qty_round_precision > 3:
|
||||
qty_round_precision = 3
|
||||
rounded = np.round(roundable_qty, qty_round_precision)
|
||||
# for qty rounding down is important to prevent InsufficenMargin
|
||||
rounded = round_decimals_down(roundable_qty, precision)
|
||||
|
||||
for index, q in enumerate(rounded):
|
||||
if q == 0.0:
|
||||
rounded[index] = 0.001
|
||||
rounded[index] = 1 / 10 ** precision
|
||||
|
||||
return rounded
|
||||
|
||||
|
||||
def round_decimals_down(number: np.ndarray, decimals: int = 2) -> float:
|
||||
"""
|
||||
Returns a value rounded down to a specific number of decimal places.
|
||||
"""
|
||||
if not isinstance(decimals, int):
|
||||
raise TypeError("decimal places must be an integer")
|
||||
elif decimals < 0:
|
||||
raise ValueError("decimal places has to be 0 or more")
|
||||
elif decimals == 0:
|
||||
return np.floor(number)
|
||||
|
||||
factor = 10 ** decimals
|
||||
return np.floor(number * factor) / factor
|
||||
|
||||
|
||||
def same_length(bigger: np.ndarray, shorter: np.ndarray) -> np.ndarray:
|
||||
return np.concatenate((np.full((bigger.shape[0] - shorter.shape[0]), np.nan), shorter))
|
||||
|
||||
|
||||
def secure_hash(msg: str) -> str:
|
||||
return hashlib.sha256(msg.encode()).hexdigest()
|
||||
|
||||
@@ -621,6 +657,9 @@ def should_execute_silently() -> bool:
|
||||
def side_to_type(s: str) -> str:
|
||||
from jesse.enums import trade_types, sides
|
||||
|
||||
# make sure string is lowercase
|
||||
s = s.lower()
|
||||
|
||||
if s == sides.BUY:
|
||||
return trade_types.LONG
|
||||
if s == sides.SELL:
|
||||
@@ -628,13 +667,20 @@ def side_to_type(s: str) -> str:
|
||||
raise ValueError
|
||||
|
||||
|
||||
def string_after_character(string: str, character: str) -> str:
|
||||
def string_after_character(s: str, character: str) -> str:
|
||||
try:
|
||||
return string.split(character, 1)[1]
|
||||
return s.split(character, 1)[1]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
|
||||
def slice_candles(candles: np.ndarray, sequential: bool) -> np.ndarray:
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and candles.shape[0] > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
return candles
|
||||
|
||||
|
||||
def style(msg_text: str, msg_style: str) -> str:
|
||||
if msg_style is None:
|
||||
return msg_text
|
||||
@@ -650,16 +696,32 @@ def style(msg_text: str, msg_style: str) -> str:
|
||||
|
||||
def terminate_app() -> None:
|
||||
# close the database
|
||||
from jesse.services.db import close_connection
|
||||
close_connection()
|
||||
from jesse.services.db import database
|
||||
database.close_connection()
|
||||
# disconnect python from the OS
|
||||
os._exit(1)
|
||||
|
||||
|
||||
def error(msg: str, force_print: bool = False) -> None:
|
||||
# send notifications if it's a live session
|
||||
if is_live():
|
||||
from jesse.services import logger
|
||||
logger.error(msg)
|
||||
if force_print:
|
||||
_print_error(msg)
|
||||
else:
|
||||
_print_error(msg)
|
||||
|
||||
|
||||
def _print_error(msg: str) -> None:
|
||||
print('\n')
|
||||
print(color('========== critical error =========='.upper(), 'red'))
|
||||
print(color(msg, 'red'))
|
||||
|
||||
|
||||
def timeframe_to_one_minutes(timeframe: str) -> int:
|
||||
from jesse.enums import timeframes
|
||||
from jesse.exceptions import InvalidTimeframe
|
||||
all_timeframes = [timeframe for timeframe in class_iter(timeframes)]
|
||||
|
||||
dic = {
|
||||
timeframes.MINUTE_1: 1,
|
||||
@@ -676,16 +738,14 @@ def timeframe_to_one_minutes(timeframe: str) -> int:
|
||||
timeframes.HOUR_8: 60 * 8,
|
||||
timeframes.HOUR_12: 60 * 12,
|
||||
timeframes.DAY_1: 60 * 24,
|
||||
timeframes.DAY_3: 60 * 24 * 3,
|
||||
timeframes.WEEK_1: 60 * 24 * 7,
|
||||
}
|
||||
|
||||
try:
|
||||
return dic[timeframe]
|
||||
except KeyError:
|
||||
all_timeframes = [timeframe for timeframe in class_iter(timeframes)]
|
||||
raise InvalidTimeframe(
|
||||
'Timeframe "{}" is invalid. Supported timeframes are {}.'.format(
|
||||
timeframe, ', '.join(all_timeframes)))
|
||||
f'Timeframe "{timeframe}" is invalid. Supported timeframes are {", ".join(all_timeframes)}.')
|
||||
|
||||
|
||||
def timestamp_to_arrow(timestamp: int) -> arrow.arrow.Arrow:
|
||||
@@ -728,3 +788,165 @@ def unique_list(arr) -> list:
|
||||
seen = set()
|
||||
seen_add = seen.add
|
||||
return [x for x in arr if not (x in seen or seen_add(x))]
|
||||
|
||||
|
||||
def closing_side(position_type: str) -> str:
|
||||
if position_type.lower() == 'long':
|
||||
return 'sell'
|
||||
elif position_type.lower() == 'short':
|
||||
return 'buy'
|
||||
else:
|
||||
raise ValueError(f'Value entered for position_type ({position_type}) is not valid')
|
||||
|
||||
|
||||
def merge_dicts(d1: dict, d2: dict) -> dict:
|
||||
"""
|
||||
Merges nested dictionaries
|
||||
|
||||
:param d1: dict
|
||||
:param d2: dict
|
||||
:return: dict
|
||||
"""
|
||||
def inner(dict1, dict2):
|
||||
for k in set(dict1.keys()).union(dict2.keys()):
|
||||
if k in dict1 and k in dict2:
|
||||
if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
|
||||
yield k, dict(merge_dicts(dict1[k], dict2[k]))
|
||||
else:
|
||||
yield k, dict2[k]
|
||||
elif k in dict1:
|
||||
yield k, dict1[k]
|
||||
else:
|
||||
yield k, dict2[k]
|
||||
|
||||
return dict(inner(d1, d2))
|
||||
|
||||
|
||||
def computer_name():
|
||||
import platform
|
||||
return platform.node()
|
||||
|
||||
|
||||
def validate_response(response):
|
||||
if response.status_code != 200:
|
||||
err_msg = f"[{response.status_code}]: {response.json()['message']}\nPlease contact us at support@jesse.trade if this is unexpected."
|
||||
|
||||
if response.status_code not in [401, 403]:
|
||||
raise ConnectionError(err_msg)
|
||||
error(err_msg, force_print=True)
|
||||
terminate_app()
|
||||
|
||||
|
||||
def get_session_id():
|
||||
from jesse.store import store
|
||||
return store.app.session_id
|
||||
|
||||
|
||||
def get_pid():
|
||||
return os.getpid()
|
||||
|
||||
|
||||
def is_jesse_project():
|
||||
ls = os.listdir('.')
|
||||
return 'strategies' in ls and 'storage' in ls
|
||||
|
||||
|
||||
def dd(item, pretty=True):
|
||||
"""
|
||||
Dump and Die but pretty: used for debugging when developing Jesse
|
||||
"""
|
||||
dump(item, pretty)
|
||||
terminate_app()
|
||||
|
||||
|
||||
def dump(item, pretty=True):
|
||||
"""
|
||||
Dump object in pretty format: used for debugging when developing Jesse
|
||||
"""
|
||||
print(
|
||||
color('\n========= Debugging Value =========='.upper(), 'yellow')
|
||||
)
|
||||
|
||||
if pretty:
|
||||
pprint(item)
|
||||
else:
|
||||
print(item)
|
||||
|
||||
print(
|
||||
color('====================================\n', 'yellow')
|
||||
)
|
||||
|
||||
|
||||
def float_or_none(item):
|
||||
"""
|
||||
Return the float of the value if it's not None
|
||||
"""
|
||||
if item is None:
|
||||
return None
|
||||
else:
|
||||
return float(item)
|
||||
|
||||
|
||||
def str_or_none(item, encoding='utf-8'):
|
||||
"""
|
||||
Return the str of the value if it's not None
|
||||
"""
|
||||
if item is None:
|
||||
return None
|
||||
else:
|
||||
# return item if it's str, if not, decode it using encoding
|
||||
if isinstance(item, str):
|
||||
return item
|
||||
return str(item, encoding)
|
||||
|
||||
|
||||
def get_settlement_currency_from_exchange(exchange: str):
|
||||
if exchange in {'FTX Futures', 'Bitfinex', 'Coinbase'}:
|
||||
return 'USD'
|
||||
else:
|
||||
return 'USDT'
|
||||
|
||||
|
||||
def cpu_cores_count():
|
||||
from multiprocessing import cpu_count
|
||||
return cpu_count()
|
||||
|
||||
|
||||
# a function that converts name to env_name. Example: 'Testnet Binance Futures' into 'TESTNET_BINANCE_FUTURES'
|
||||
def convert_to_env_name(name: str) -> str:
|
||||
return name.replace(' ', '_').upper()
|
||||
|
||||
|
||||
def is_notebook():
|
||||
try:
|
||||
shell = get_ipython().__class__.__name__
|
||||
# Jupyter notebook or qtconsole
|
||||
if shell == 'ZMQInteractiveShell':
|
||||
return True
|
||||
elif shell == 'TerminalInteractiveShell':
|
||||
# Terminal running IPython
|
||||
return False
|
||||
else:
|
||||
# Other type (?)
|
||||
return False
|
||||
except NameError:
|
||||
# Probably standard Python interpreter
|
||||
return False
|
||||
|
||||
|
||||
def get_os() -> str:
|
||||
import platform
|
||||
if platform.system() == 'Darwin':
|
||||
return 'mac'
|
||||
elif platform.system() == 'Linux':
|
||||
return 'linux'
|
||||
elif platform.system() == 'Windows':
|
||||
return 'windows'
|
||||
else:
|
||||
raise NotImplementedError(f'Unsupported OS: "{platform.system()}"')
|
||||
|
||||
|
||||
# a function that returns boolean whether or not the code is being executed inside a docker container
|
||||
def is_docker() -> bool:
|
||||
import os
|
||||
return os.path.exists('/.dockerenv')
|
||||
|
||||
@@ -4,12 +4,14 @@ from .adosc import adosc
|
||||
from .adx import adx
|
||||
from .adxr import adxr
|
||||
from .alligator import alligator
|
||||
from .alma import alma
|
||||
from .ao import ao
|
||||
from .apo import apo
|
||||
from .aroon import aroon
|
||||
from .aroonosc import aroonosc
|
||||
from .atr import atr
|
||||
from .avgprice import avgprice
|
||||
from .bandpass import bandpass
|
||||
from .beta import beta
|
||||
from .bollinger_bands import bollinger_bands
|
||||
from .bollinger_bands_width import bollinger_bands_width
|
||||
@@ -19,10 +21,13 @@ from .cci import cci
|
||||
from .cfo import cfo
|
||||
from .cg import cg
|
||||
from .chande import chande
|
||||
from .chop import chop
|
||||
from .cksp import cksp
|
||||
from .cmo import cmo
|
||||
from .correl import correl
|
||||
from .correlation_cycle import correlation_cycle
|
||||
from .cvi import cvi
|
||||
from .cwma import cwma
|
||||
from .damiani_volatmeter import damiani_volatmeter
|
||||
from .dec_osc import dec_osc
|
||||
from .decycler import decycler
|
||||
@@ -34,11 +39,14 @@ from .donchian import donchian
|
||||
from .dpo import dpo
|
||||
from .dti import dti
|
||||
from .dx import dx
|
||||
from .edcf import edcf
|
||||
from .efi import efi
|
||||
from .ema import ema
|
||||
from .emd import emd
|
||||
from .emv import emv
|
||||
from .epma import epma
|
||||
from .er import er
|
||||
from .eri import eri
|
||||
from .fisher import fisher
|
||||
from .fosc import fosc
|
||||
from .frama import frama
|
||||
@@ -54,25 +62,37 @@ from .ht_phasor import ht_phasor
|
||||
from .ht_sine import ht_sine
|
||||
from .ht_trendline import ht_trendline
|
||||
from .ht_trendmode import ht_trendmode
|
||||
from .hurst_exponent import hurst_exponent
|
||||
from .hwma import hwma
|
||||
from .ichimoku_cloud import ichimoku_cloud
|
||||
from .ichimoku_cloud_seq import ichimoku_cloud_seq
|
||||
from .ift_rsi import ift_rsi
|
||||
from .itrend import itrend
|
||||
from .jma import jma
|
||||
from .jsa import jsa
|
||||
from .kama import kama
|
||||
from .kaufmanstop import kaufmanstop
|
||||
from .kdj import kdj
|
||||
from .keltner import keltner
|
||||
from .kst import kst
|
||||
from .kurtosis import kurtosis
|
||||
from .kvo import kvo
|
||||
from .linearreg import linearreg
|
||||
from .linearreg_angle import linearreg_angle
|
||||
from .linearreg_intercept import linearreg_intercept
|
||||
from .linearreg_slope import linearreg_slope
|
||||
from .lrsi import lrsi
|
||||
from .ma import ma
|
||||
from .maaq import maaq
|
||||
from .mab import mab
|
||||
from .macd import macd
|
||||
from .macdext import macdext
|
||||
from .mama import mama
|
||||
from .marketfi import marketfi
|
||||
from .mass import mass
|
||||
from .mcginley_dynamic import mcginley_dynamic
|
||||
from .mean_ad import mean_ad
|
||||
from .median_ad import median_ad
|
||||
from .medprice import medprice
|
||||
from .mfi import mfi
|
||||
from .midpoint import midpoint
|
||||
@@ -80,15 +100,21 @@ from .midprice import midprice
|
||||
from .minmax import minmax
|
||||
from .mom import mom
|
||||
from .msw import msw
|
||||
from .mwdx import mwdx
|
||||
from .natr import natr
|
||||
from .nma import nma
|
||||
from .nvi import nvi
|
||||
from .obv import obv
|
||||
from .pattern_recognition import pattern_recognition
|
||||
from .pfe import pfe
|
||||
from .pivot import pivot
|
||||
from .pma import pma
|
||||
from .ppo import ppo
|
||||
from .pvi import pvi
|
||||
from .pwma import pwma
|
||||
from .qstick import qstick
|
||||
from .reflex import reflex
|
||||
from .rma import rma
|
||||
from .roc import roc
|
||||
from .rocp import rocp
|
||||
from .rocr import rocr
|
||||
@@ -102,9 +128,12 @@ from .safezonestop import safezonestop
|
||||
from .sar import sar
|
||||
from .sarext import sarext
|
||||
from .sinwma import sinwma
|
||||
from .skew import skew
|
||||
from .sma import sma
|
||||
from .smma import smma
|
||||
from .sqwma import sqwma
|
||||
from .srsi import srsi
|
||||
from .srwma import srwma
|
||||
from .stc import stc
|
||||
from .stddev import stddev
|
||||
from .stochastic import stoch
|
||||
@@ -112,6 +141,7 @@ from .stochf import stochf
|
||||
from .supersmoother import supersmoother
|
||||
from .supersmoother_3_pole import supersmoother_3_pole
|
||||
from .supertrend import supertrend
|
||||
from .swma import swma
|
||||
from .t3 import t3
|
||||
from .tema import tema
|
||||
from .trange import trange
|
||||
@@ -120,22 +150,27 @@ from .trima import trima
|
||||
from .trix import trix
|
||||
from .tsf import tsf
|
||||
from .tsi import tsi
|
||||
from .ttm_trend import ttm_trend
|
||||
from .typprice import typprice
|
||||
from .ui import ui
|
||||
from .ultosc import ultosc
|
||||
from .var import var
|
||||
from .vi import vi
|
||||
from .vidya import vidya
|
||||
from .vlma import vlma
|
||||
from .vosc import vosc
|
||||
from .voss import voss
|
||||
from .vpci import vpci
|
||||
from .vpt import vpt
|
||||
from .vpwma import vpwma
|
||||
from .vwap import vwap
|
||||
from .vwma import vwma
|
||||
from .vwmacd import vwmacd
|
||||
from .vwap import vwap
|
||||
from .wad import wad
|
||||
from .wclprice import wclprice
|
||||
from .wilders import wilders
|
||||
from .willr import willr
|
||||
from .wma import wma
|
||||
from .wt import wt
|
||||
from .zlema import zlema
|
||||
from .zscore import zscore
|
||||
|
||||
@@ -3,7 +3,7 @@ from collections import namedtuple
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
AC = namedtuple('AC', ['osc', 'change'])
|
||||
|
||||
@@ -13,13 +13,11 @@ def acosc(candles: np.ndarray, sequential: bool = False) -> AC:
|
||||
Acceleration / Deceleration Oscillator (AC)
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: AC(osc, change)
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
med = talib.MEDPRICE(candles[:, 3], candles[:, 4])
|
||||
ao = talib.SMA(med, 5) - talib.SMA(med, 34)
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def ad(candles: np.ndarray, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
@@ -11,17 +11,12 @@ def ad(candles: np.ndarray, sequential: bool = False) -> Union[float, np.ndarray
|
||||
AD - Chaikin A/D Line
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
res = talib.AD(candles[:, 3], candles[:, 4], candles[:, 2], candles[:, 5])
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def adosc(candles: np.ndarray, fast_period: int = 3, slow_period: int = 10, sequential: bool = False) -> Union[
|
||||
@@ -14,18 +14,13 @@ def adosc(candles: np.ndarray, fast_period: int = 3, slow_period: int = 10, sequ
|
||||
:param candles: np.ndarray
|
||||
:param fast_period: int - default: 3
|
||||
:param slow_period: int - default: 10
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
res = talib.ADOSC(candles[:, 3], candles[:, 4], candles[:, 2], candles[:, 5], fastperiod=fast_period,
|
||||
slowperiod=slow_period)
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def adx(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
@@ -11,18 +11,13 @@ def adx(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Unio
|
||||
ADX - Average Directional Movement Index
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default=14
|
||||
:param sequential: bool - default=False
|
||||
:param period: int - default: 14
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
res = talib.ADX(candles[:, 3], candles[:, 4], candles[:, 2], timeperiod=period)
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def adxr(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
@@ -11,18 +11,13 @@ def adxr(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Uni
|
||||
ADXR - Average Directional Movement Index Rating
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default=14
|
||||
:param sequential: bool - default=False
|
||||
:param period: int - default: 14
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
res = talib.ADXR(candles[:, 3], candles[:, 4], candles[:, 2], timeperiod=period)
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -2,8 +2,7 @@ from collections import namedtuple
|
||||
|
||||
import numpy as np
|
||||
|
||||
from jesse.helpers import get_candle_source, np_shift
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import get_candle_source, np_shift, slice_candles
|
||||
|
||||
AG = namedtuple('AG', ['jaw', 'teeth', 'lips'])
|
||||
|
||||
@@ -14,13 +13,11 @@ def alligator(candles: np.ndarray, source_type: str = "close", sequential: bool
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: AG(jaw, teeth, lips)
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
@@ -42,13 +39,11 @@ def numpy_ewma(data: np.ndarray, window: int):
|
||||
:return:
|
||||
"""
|
||||
alpha = 1 / window
|
||||
scale = 1 / (1 - alpha)
|
||||
# scale = 1 / (1 - alpha)
|
||||
n = data.shape[0]
|
||||
scale_arr = (1 - alpha) ** (-1 * np.arange(n))
|
||||
weights = (1 - alpha) ** np.arange(n)
|
||||
pw0 = (1 - alpha) ** (n - 1)
|
||||
mult = data * pw0 * scale_arr
|
||||
cumsums = mult.cumsum()
|
||||
out = cumsums * scale_arr[::-1] / weights.cumsum()
|
||||
|
||||
return out
|
||||
return cumsums * scale_arr[::-1] / weights.cumsum()
|
||||
|
||||
54
jesse/indicators/alma.py
Normal file
54
jesse/indicators/alma.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
def alma(candles: np.ndarray, period: int = 9, sigma: float = 6.0, distribution_offset: float = 0.85,
|
||||
source_type: str = "close", sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
ALMA - Arnaud Legoux Moving Average
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 9
|
||||
:param sigma: float - default: 6.0
|
||||
:param distribution_offset: float - default: 0.85
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
if len(candles.shape) == 1:
|
||||
source = candles
|
||||
else:
|
||||
candles = slice_candles(candles, sequential)
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
asize = period - 1
|
||||
m = distribution_offset * asize
|
||||
s = period / sigma
|
||||
dss = 2 * s * s
|
||||
|
||||
wtds = np.exp(-(np.arange(period) - m) ** 2 / dss)
|
||||
pnp_array3D = strided_axis0(source, len(wtds))
|
||||
res = np.zeros(source.shape)
|
||||
res[period - 1:] = np.tensordot(pnp_array3D, wtds, axes=(1, 0))[:]
|
||||
res /= wtds.sum()
|
||||
res[res == 0] = np.nan
|
||||
|
||||
|
||||
return res if sequential else res[-1]
|
||||
|
||||
|
||||
def strided_axis0(a, L):
|
||||
# Store the shape and strides info
|
||||
shp = a.shape
|
||||
s = a.strides
|
||||
# Compute length of output array along the first axis
|
||||
nd0 = shp[0] - L + 1
|
||||
# Setup shape and strides for use with np.lib.stride_tricks.as_strided
|
||||
# and get (n+1) dim output array
|
||||
shp_in = (nd0, L) + shp[1:]
|
||||
strd_in = (s[0],) + s
|
||||
return np.lib.stride_tricks.as_strided(a, shape=shp_in, strides=strd_in)
|
||||
@@ -3,7 +3,7 @@ from collections import namedtuple
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
AO = namedtuple('AO', ['osc', 'change'])
|
||||
|
||||
@@ -13,13 +13,11 @@ def ao(candles: np.ndarray, sequential: bool = False) -> AO:
|
||||
Awesome Oscillator
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: AO(osc, change)
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
med = talib.MEDPRICE(candles[:, 3], candles[:, 4])
|
||||
res = talib.SMA(med, 5) - talib.SMA(med, 34)
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
import talib
|
||||
from jesse.indicators.ma import ma
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
|
||||
def apo(candles: np.ndarray, fast_period: int = 12, slow_period: int = 26, matype: int = 0, source_type: str = "close",
|
||||
@@ -17,16 +16,14 @@ def apo(candles: np.ndarray, fast_period: int = 12, slow_period: int = 26, matyp
|
||||
:param slow_period: int - default: 26
|
||||
:param matype: int - default: 0
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
res = talib.APO(source, fastperiod=fast_period, slowperiod=slow_period, matype=matype)
|
||||
res = ma(source, period=fast_period, matype=matype, sequential=True) - ma(source, period=slow_period, matype=matype, sequential=True)
|
||||
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -3,7 +3,7 @@ from collections import namedtuple
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
AROON = namedtuple('AROON', ['down', 'up'])
|
||||
|
||||
@@ -13,14 +13,12 @@ def aroon(candles: np.ndarray, period: int = 14, sequential: bool = False) -> AR
|
||||
AROON - Aroon
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default=14
|
||||
:param sequential: bool - default=False
|
||||
:param period: int - default: 14
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: AROON(down, up)
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
aroondown, aroonup = talib.AROON(candles[:, 3], candles[:, 4], timeperiod=period)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def aroonosc(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
@@ -11,18 +11,13 @@ def aroonosc(candles: np.ndarray, period: int = 14, sequential: bool = False) ->
|
||||
AROONOSC - Aroon Oscillator
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default=14
|
||||
:param sequential: bool - default=False
|
||||
:param period: int - default: 14
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
res = talib.AROONOSC(candles[:, 3], candles[:, 4], timeperiod=period)
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def atr(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
@@ -11,18 +11,13 @@ def atr(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Unio
|
||||
ATR - Average True Range
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default=14
|
||||
:param sequential: bool - default=False
|
||||
:param period: int - default: 14
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
res = talib.ATR(candles[:, 3], candles[:, 4], candles[:, 2], timeperiod=period)
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def avgprice(candles: np.ndarray, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
@@ -11,17 +11,12 @@ def avgprice(candles: np.ndarray, sequential: bool = False) -> Union[float, np.n
|
||||
AVGPRICE - Average Price
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
res = talib.AVGPRICE(candles[:, 1], candles[:, 3], candles[:, 4], candles[:, 2])
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
return res if sequential else res[-1]
|
||||
|
||||
68
jesse/indicators/bandpass.py
Normal file
68
jesse/indicators/bandpass.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from collections import namedtuple
|
||||
|
||||
import numpy as np
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from .high_pass import high_pass_fast
|
||||
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
BandPass = namedtuple('BandPass', ['bp', 'bp_normalized', 'signal', 'trigger'])
|
||||
|
||||
|
||||
def bandpass(candles: np.ndarray, period: int = 20, bandwidth: float = 0.3, source_type: str = "close", sequential: bool = False) -> BandPass:
|
||||
"""
|
||||
BandPass Filter
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 20
|
||||
:param bandwidth: float - default: 0.3
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: BandPass(bp, bp_normalized, signal, trigger)
|
||||
"""
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
|
||||
hp = high_pass_fast(source, 4 * period / bandwidth)
|
||||
|
||||
beta = np.cos(2 * np.pi / period)
|
||||
gamma = np.cos(2 * np.pi * bandwidth / period)
|
||||
alpha = 1 / gamma - np.sqrt(1 / gamma ** 2 - 1)
|
||||
|
||||
bp, peak = bp_fast(source, hp, alpha, beta)
|
||||
|
||||
bp_normalized = bp / peak
|
||||
|
||||
trigger = high_pass_fast(bp_normalized, period / bandwidth / 1.5)
|
||||
signal = (bp_normalized < trigger) * 1 - (trigger < bp_normalized) * 1
|
||||
|
||||
if sequential:
|
||||
return BandPass(bp, bp_normalized, signal, trigger)
|
||||
else:
|
||||
return BandPass(bp[-1], bp_normalized[-1], signal[-1], trigger[-1])
|
||||
|
||||
|
||||
@njit
|
||||
def bp_fast(source, hp, alpha, beta): # Function is compiled to machine code when called the first time
|
||||
|
||||
bp = np.copy(hp)
|
||||
for i in range(2, source.shape[0]):
|
||||
bp[i] = 0.5 * (1 - alpha) * hp[i] - (1 - alpha) * 0.5 * hp[i - 2] + beta * (1 + alpha) * bp[i - 1] - alpha * bp[i - 2]
|
||||
|
||||
# fast attack-slow decay AGC
|
||||
K = 0.991
|
||||
peak = np.copy(bp)
|
||||
for i in range(source.shape[0]):
|
||||
if i > 0:
|
||||
peak[i] = peak[i - 1] * K
|
||||
if np.abs(bp[i]) > peak[i]:
|
||||
peak[i] = np.abs(bp[i])
|
||||
|
||||
return bp, peak
|
||||
@@ -3,7 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def beta(candles: np.ndarray, period: int = 5, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
@@ -12,17 +12,12 @@ def beta(candles: np.ndarray, period: int = 5, sequential: bool = False) -> Unio
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 5
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
res = talib.BETA(candles[:, 3], candles[:, 4], timeperiod=period)
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -2,14 +2,16 @@ from collections import namedtuple
|
||||
|
||||
import numpy as np
|
||||
import talib
|
||||
from jesse.indicators.ma import ma
|
||||
from jesse.indicators.mean_ad import mean_ad
|
||||
from jesse.indicators.median_ad import median_ad
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
BollingerBands = namedtuple('BollingerBands', ['upperband', 'middleband', 'lowerband'])
|
||||
|
||||
|
||||
def bollinger_bands(candles: np.ndarray, period: int = 20, devup: float = 2, devdn: float = 2, matype: int = 0,
|
||||
def bollinger_bands(candles: np.ndarray, period: int = 20, devup: float = 2, devdn: float = 2, matype: int = 0, devtype: int = 0,
|
||||
source_type: str = "close",
|
||||
sequential: bool = False) -> BollingerBands:
|
||||
"""
|
||||
@@ -20,18 +22,27 @@ def bollinger_bands(candles: np.ndarray, period: int = 20, devup: float = 2, dev
|
||||
:param devup: float - default: 2
|
||||
:param devdn: float - default: 2
|
||||
:param matype: int - default: 0
|
||||
:param devtype: int - default: 0
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: BollingerBands(upperband, middleband, lowerband)
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
upperbands, middlebands, lowerbands = talib.BBANDS(source, timeperiod=period, nbdevup=devup, nbdevdn=devdn,
|
||||
matype=matype)
|
||||
|
||||
if devtype == 0:
|
||||
dev = talib.STDDEV(source, period)
|
||||
elif devtype == 1:
|
||||
dev = mean_ad(source, period, sequential=True)
|
||||
elif devtype == 2:
|
||||
dev = median_ad(source, period, sequential=True)
|
||||
|
||||
middlebands = ma(source, period=period, matype=matype, sequential=True)
|
||||
upperbands = middlebands + devup * dev
|
||||
lowerbands = middlebands - devdn * dev
|
||||
|
||||
|
||||
if sequential:
|
||||
return BollingerBands(upperbands, middlebands, lowerbands)
|
||||
|
||||
@@ -2,14 +2,17 @@ from typing import Union
|
||||
|
||||
import numpy as np
|
||||
import talib
|
||||
from jesse.indicators.ma import ma
|
||||
from jesse.indicators.mean_ad import mean_ad
|
||||
from jesse.indicators.median_ad import median_ad
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
|
||||
def bollinger_bands_width(candles: np.ndarray, period: int = 20, devup: float = 2, devdn: float = 2, matype: int = 0,
|
||||
source_type: str = "close",
|
||||
sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
devtype: int = 0,
|
||||
source_type: str = "close",
|
||||
sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
BBW - Bollinger Bands Width - Bollinger Bands Bandwidth
|
||||
|
||||
@@ -18,20 +21,31 @@ def bollinger_bands_width(candles: np.ndarray, period: int = 20, devup: float =
|
||||
:param devup: float - default: 2
|
||||
:param devdn: float - default: 2
|
||||
:param matype: int - default: 0
|
||||
:param devtype: int - default: 0
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
:return: BollingerBands(upperband, middleband, lowerband)
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
upperbands, middlebands, lowerbands = talib.BBANDS(source, timeperiod=period, nbdevup=devup, nbdevdn=devdn,
|
||||
matype=matype)
|
||||
|
||||
if devtype == 0:
|
||||
dev = talib.STDDEV(source, period)
|
||||
elif devtype == 1:
|
||||
dev = mean_ad(source, period, sequential=True)
|
||||
elif devtype == 2:
|
||||
dev = median_ad(source, period, sequential=True)
|
||||
|
||||
middlebands = ma(source, period=period, matype=matype, sequential=True)
|
||||
upperbands = middlebands + devup * dev
|
||||
lowerbands = middlebands - devdn * dev
|
||||
|
||||
if sequential:
|
||||
return (upperbands - lowerbands) / middlebands
|
||||
else:
|
||||
return (upperbands[-1] - lowerbands[-1]) / middlebands[-1]
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def bop(candles: np.ndarray, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
@@ -11,17 +11,12 @@ def bop(candles: np.ndarray, sequential: bool = False) -> Union[float, np.ndarra
|
||||
BOP - Balance Of Power
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
res = talib.BOP(candles[:, 1], candles[:, 3], candles[:, 4], candles[:, 2])
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -3,8 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
|
||||
def cc(candles: np.ndarray, wma_period: int = 10, roc_short_period: int = 11, roc_long_period: int = 14,
|
||||
@@ -18,13 +17,11 @@ def cc(candles: np.ndarray, wma_period: int = 10, roc_short_period: int = 11, ro
|
||||
:param roc_short_period: int - default: 11
|
||||
:param roc_long_period: int - default: 14
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
res = talib.WMA(talib.ROC(source, timeperiod=roc_long_period) + talib.ROC(source, timeperiod=roc_short_period),
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def cci(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
@@ -11,18 +11,13 @@ def cci(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Unio
|
||||
CCI - Commodity Channel Index
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default=14
|
||||
:param sequential: bool - default=False
|
||||
:param period: int - default: 14
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
res = talib.CCI(candles[:, 3], candles[:, 4], candles[:, 2], timeperiod=period)
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -4,7 +4,7 @@ import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def cfo(candles: np.ndarray, period: int = 14, scalar: float = 100, source_type: str = "close",
|
||||
@@ -14,22 +14,21 @@ def cfo(candles: np.ndarray, period: int = 14, scalar: float = 100, source_type:
|
||||
CFO - Chande Forcast Oscillator
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default=14
|
||||
:param period: int - default: 14
|
||||
:param scalar: float - default: 100
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
cfo = scalar * (source - talib.LINEARREG(source, timeperiod=period))
|
||||
cfo /= source
|
||||
res = scalar * (source - talib.LINEARREG(source, timeperiod=period))
|
||||
res /= source
|
||||
|
||||
if sequential:
|
||||
return cfo
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(cfo[-1]) else cfo[-1]
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
from numba import njit
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import get_candle_source, same_length, slice_candles
|
||||
|
||||
|
||||
def cg(candles: np.ndarray, period: int = 10, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
@@ -15,32 +17,30 @@ def cg(candles: np.ndarray, period: int = 10, source_type: str = "close", sequen
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 10
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
res = go_fast(source, period)
|
||||
|
||||
return np.concatenate((np.full((candles.shape[0] - res.shape[0]), np.nan), res), axis=0) if sequential else res[-1]
|
||||
return same_length(candles, res) if sequential else res[-1]
|
||||
|
||||
|
||||
@njit
|
||||
def go_fast(source, period): # Function is compiled to machine code when called the first time
|
||||
res = np.full_like(source, fill_value=np.nan)
|
||||
for i in range(0, len(source)):
|
||||
for i in range(source.size):
|
||||
if i > period:
|
||||
num = 0
|
||||
denom = 0
|
||||
for count in range(0, period - 1):
|
||||
for count in range(period - 1):
|
||||
close = source[i - count]
|
||||
if not np.isnan(close):
|
||||
num = num + (1 + count) * close
|
||||
denom = denom + close
|
||||
num += (1 + count) * close
|
||||
denom += close
|
||||
result = -num / denom if denom != 0 else 0
|
||||
res[i] = result
|
||||
return res
|
||||
|
||||
@@ -4,7 +4,7 @@ import numpy as np
|
||||
import talib
|
||||
from scipy.ndimage.filters import maximum_filter1d, minimum_filter1d
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def chande(candles: np.ndarray, period: int = 22, mult: float = 3.0, direction: str = "long",
|
||||
@@ -14,15 +14,13 @@ def chande(candles: np.ndarray, period: int = 22, mult: float = 3.0, direction:
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 22
|
||||
:param period: float - default: 3.0
|
||||
:param mult: float - default: 3.0
|
||||
:param direction: str - default: "long"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
candles_close = candles[:, 2]
|
||||
candles_high = candles[:, 3]
|
||||
@@ -42,10 +40,10 @@ def chande(candles: np.ndarray, period: int = 22, mult: float = 3.0, direction:
|
||||
return result if sequential else result[-1]
|
||||
|
||||
|
||||
def filter1d_same(a: np.ndarray, W: int, type: str, fillna=np.nan):
|
||||
def filter1d_same(a: np.ndarray, W: int, max_or_min: str, fillna=np.nan):
|
||||
out_dtype = np.full(0, fillna).dtype
|
||||
hW = (W - 1) // 2 # Half window size
|
||||
if type == 'max':
|
||||
if max_or_min == 'max':
|
||||
out = maximum_filter1d(a, size=W, origin=hW)
|
||||
else:
|
||||
out = minimum_filter1d(a, size=W, origin=hW)
|
||||
|
||||
36
jesse/indicators/chop.py
Normal file
36
jesse/indicators/chop.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def chop(candles: np.ndarray, period: int = 14, scalar: float = 100, drift: int = 1, sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
Choppiness Index (CHOP)
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 30
|
||||
:param scalar: float - default: 100
|
||||
:param drift: int - default: 1
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
|
||||
candles_close = candles[:, 2]
|
||||
candles_high = candles[:, 3]
|
||||
candles_low = candles[:, 4]
|
||||
|
||||
atr_sum = talib.SUM(talib.ATR(candles_high, candles_low, candles_close, timeperiod=drift), period)
|
||||
|
||||
hh = talib.MAX(candles_high, period)
|
||||
ll = talib.MIN(candles_low, period)
|
||||
|
||||
res = (scalar * (np.log10(atr_sum) - np.log10(hh - ll))) / np.log10(period)
|
||||
|
||||
return res if sequential else res[-1]
|
||||
41
jesse/indicators/cksp.py
Normal file
41
jesse/indicators/cksp.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from collections import namedtuple
|
||||
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
CKSP = namedtuple('CKSP', ['long', 'short'])
|
||||
|
||||
def cksp(candles: np.ndarray, p: int = 10, x: float = 1.0, q: int = 9, sequential: bool = False) -> CKSP:
|
||||
"""
|
||||
Chande Kroll Stop (CKSP)
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param p: int - default: 10
|
||||
:param x: float - default: 1.0
|
||||
:param q: int - default: 9
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
candles_close = candles[:, 2]
|
||||
candles_high = candles[:, 3]
|
||||
candles_low = candles[:, 4]
|
||||
|
||||
atr = talib.ATR(candles_high, candles_low, candles_close, timeperiod=p)
|
||||
|
||||
LS0 = talib.MAX(candles_high, q) - x * atr
|
||||
LS = talib.MAX(LS0, q)
|
||||
|
||||
SS0 = talib.MIN(candles_low, q) + x * atr
|
||||
SS = talib.MIN(SS0, q)
|
||||
|
||||
if sequential:
|
||||
return CKSP(LS, SS)
|
||||
else:
|
||||
return CKSP(LS[-1], SS[-1])
|
||||
|
||||
@@ -4,7 +4,7 @@ import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def cmo(candles: np.ndarray, period: int = 14, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
@@ -13,20 +13,15 @@ def cmo(candles: np.ndarray, period: int = 14, source_type: str = "close", seque
|
||||
CMO - Chande Momentum Oscillator
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default=14
|
||||
:param period: int - default: 14
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
res = talib.CMO(source, timeperiod=period)
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def correl(candles: np.ndarray, period: int = 5, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
@@ -12,17 +12,12 @@ def correl(candles: np.ndarray, period: int = 5, sequential: bool = False) -> Un
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 5
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
res = talib.CORREL(candles[:, 3], candles[:, 4], timeperiod=period)
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from collections import namedtuple
|
||||
|
||||
import numpy as np
|
||||
from numba import njit
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import get_candle_source, np_shift
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import get_candle_source, np_shift, slice_candles
|
||||
|
||||
CC = namedtuple('CC', ['real', 'imag', 'angle', 'state'])
|
||||
|
||||
@@ -18,17 +20,15 @@ def correlation_cycle(candles: np.ndarray, period: int = 20, threshold: int = 9,
|
||||
:param period: int - default: 20
|
||||
:param threshold: int - default: 9
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: CC(real, imag)
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
realPart, imagPart, angle = go_fast(source, period, threshold)
|
||||
realPart, imagPart, angle = go_fast(source, period)
|
||||
|
||||
priorAngle = np_shift(angle, 1, fill_value=np.nan)
|
||||
angle = np.where(np.logical_and(priorAngle > angle, priorAngle - angle < 270.0), priorAngle, angle)
|
||||
@@ -43,7 +43,7 @@ def correlation_cycle(candles: np.ndarray, period: int = 20, threshold: int = 9,
|
||||
|
||||
|
||||
@njit
|
||||
def go_fast(source, period, threshold): # Function is compiled to machine code when called the first time
|
||||
def go_fast(source, period): # Function is compiled to machine code when called the first time
|
||||
# Correlation Cycle Function
|
||||
PIx2 = 4.0 * np.arcsin(1.0)
|
||||
period = max(2, period)
|
||||
@@ -65,32 +65,29 @@ def go_fast(source, period, threshold): # Function is compiled to machine code
|
||||
|
||||
for j in range(period):
|
||||
jMinusOne = j + 1
|
||||
if np.isnan(source[i - jMinusOne]):
|
||||
X = 0
|
||||
else:
|
||||
X = source[i - jMinusOne]
|
||||
X = 0 if np.isnan(source[i - jMinusOne]) else source[i - jMinusOne]
|
||||
temp = PIx2 * jMinusOne / period
|
||||
Yc = np.cos(temp)
|
||||
Ys = -np.sin(temp)
|
||||
Rx = Rx + X
|
||||
Ix = Ix + X
|
||||
Rxx = Rxx + X * X
|
||||
Ixx = Ixx + X * X
|
||||
Rxy = Rxy + X * Yc
|
||||
Ixy = Ixy + X * Ys
|
||||
Ryy = Ryy + Yc * Yc
|
||||
Iyy = Iyy + Ys * Ys
|
||||
Ry = Ry + Yc
|
||||
Iy = Iy + Ys
|
||||
Rx += X
|
||||
Ix += X
|
||||
Rxx += X * X
|
||||
Ixx += X * X
|
||||
Rxy += X * Yc
|
||||
Ixy += X * Ys
|
||||
Ryy += Yc * Yc
|
||||
Iyy += Ys * Ys
|
||||
Ry += Yc
|
||||
Iy += Ys
|
||||
|
||||
temp_1 = period * Rxx - Rx * Rx
|
||||
temp_2 = period * Ryy - Ry * Ry
|
||||
if (temp_1 > 0.0 and temp_2 > 0.0):
|
||||
temp_1 = period * Rxx - Rx**2
|
||||
temp_2 = period * Ryy - Ry**2
|
||||
if temp_1 > 0.0 and temp_2 > 0.0:
|
||||
realPart[i] = (period * Rxy - Rx * Ry) / np.sqrt(temp_1 * temp_2)
|
||||
|
||||
temp_1 = period * Ixx - Ix * Ix
|
||||
temp_2 = period * Iyy - Iy * Iy
|
||||
if (temp_1 > 0.0 and temp_2 > 0.0):
|
||||
temp_1 = period * Ixx - Ix**2
|
||||
temp_2 = period * Iyy - Iy**2
|
||||
if temp_1 > 0.0 and temp_2 > 0.0:
|
||||
imagPart[i] = (period * Ixy - Ix * Iy) / np.sqrt(temp_1 * temp_2)
|
||||
|
||||
# Correlation Angle Phasor
|
||||
@@ -98,5 +95,4 @@ def go_fast(source, period, threshold): # Function is compiled to machine code
|
||||
angle = np.where(imagPart == 0, 0.0, np.degrees(np.arctan(realPart / imagPart) + HALF_OF_PI))
|
||||
angle = np.where(imagPart > 0.0, angle - 180.0, angle)
|
||||
|
||||
|
||||
return realPart, imagPart, angle
|
||||
return realPart, imagPart, angle
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
import tulipy as ti
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles, same_length
|
||||
|
||||
|
||||
def cvi(candles: np.ndarray, period: int = 5, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
@@ -12,14 +12,12 @@ def cvi(candles: np.ndarray, period: int = 5, sequential: bool = False) -> Union
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 5
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
res = ti.cvi(np.ascontiguousarray(candles[:, 3]), np.ascontiguousarray(candles[:, 4]), period=period)
|
||||
|
||||
return np.concatenate((np.full((candles.shape[0] - res.shape[0]), np.nan), res), axis=0) if sequential else res[-1]
|
||||
return same_length(candles, res) if sequential else res[-1]
|
||||
|
||||
49
jesse/indicators/cwma.py
Normal file
49
jesse/indicators/cwma.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from typing import Union
|
||||
import numpy as np
|
||||
|
||||
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
|
||||
def cwma(candles: np.ndarray, period: int = 14, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
Cubed Weighted Moving Average
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 14
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
|
||||
# Accept normal array too.
|
||||
if len(candles.shape) == 1:
|
||||
source = candles
|
||||
else:
|
||||
candles = slice_candles(candles, sequential)
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
res = vpwma_fast(source, period)
|
||||
|
||||
return res if sequential else res[-1]
|
||||
|
||||
|
||||
@njit
|
||||
def vpwma_fast(source, period):
|
||||
newseries = np.copy(source)
|
||||
for j in range(period + 1, source.shape[0]):
|
||||
my_sum = 0.0
|
||||
weightSum = 0.0
|
||||
for i in range(period - 1):
|
||||
weight = np.power(period - i, 3)
|
||||
my_sum += (source[j - i] * weight)
|
||||
weightSum += weight
|
||||
newseries[j] = my_sum / weightSum
|
||||
return newseries
|
||||
@@ -2,10 +2,13 @@ from collections import namedtuple
|
||||
|
||||
import numpy as np
|
||||
import talib
|
||||
from numba import njit
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
DamianiVolatmeter = namedtuple('DamianiVolatmeter', ['vol', 'anti'])
|
||||
|
||||
@@ -17,20 +20,18 @@ def damiani_volatmeter(candles: np.ndarray, vis_atr: int = 13, vis_std: int = 20
|
||||
Damiani Volatmeter
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param vis_atr: int - default=13
|
||||
:param vis_std: int - default=20
|
||||
:param sed_atr: int - default=40
|
||||
:param sed_std: int - default=100
|
||||
:param threshold: float - default=1.4
|
||||
:param vis_atr: int - default: 13
|
||||
:param vis_std: int - default: 20
|
||||
:param sed_atr: int - default: 40
|
||||
:param sed_std: int - default: 100
|
||||
:param threshold: float - default: 1.4
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
@@ -53,7 +54,7 @@ def damiani_volatmeter_fast(source, sed_std, atrvis, atrsed, vis_std,
|
||||
vol = np.full_like(source, 0)
|
||||
t = np.full_like(source, 0)
|
||||
for i in range(source.shape[0]):
|
||||
if not (i < sed_std):
|
||||
if i >= sed_std:
|
||||
vol[i] = atrvis[i] / atrsed[i] + lag_s * (vol[i - 1] - vol[i - 3])
|
||||
anti_thres = np.std(source[i - vis_std:i]) / np.std(source[i - sed_std:i])
|
||||
t[i] = threshold - anti_thres
|
||||
|
||||
@@ -2,8 +2,7 @@ from typing import Union
|
||||
|
||||
import numpy as np
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
from .high_pass_2_pole import high_pass_2_pole_fast
|
||||
|
||||
|
||||
@@ -13,15 +12,14 @@ def dec_osc(candles: np.ndarray, hp_period: int = 125, k: float = 1, source_type
|
||||
Ehlers Decycler Oscillator
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param hp_period: int - default=125
|
||||
:param k: float - default=1
|
||||
:param sequential: bool - default=False
|
||||
:param hp_period: int - default: 125
|
||||
:param k: float - default: 1
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
@@ -31,7 +29,4 @@ def dec_osc(candles: np.ndarray, hp_period: int = 125, k: float = 1, source_type
|
||||
|
||||
res = 100 * k * decosc / source
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -2,8 +2,7 @@ from typing import Union
|
||||
|
||||
import numpy as np
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
from .high_pass_2_pole import high_pass_2_pole_fast
|
||||
|
||||
|
||||
@@ -13,20 +12,16 @@ def decycler(candles: np.ndarray, hp_period: int = 125, source_type: str = "clos
|
||||
Ehlers Simple Decycler
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param hp_period: int - default=125
|
||||
:param sequential: bool - default=False
|
||||
:param hp_period: int - default: 125
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
hp = high_pass_2_pole_fast(source, hp_period)
|
||||
res = source - hp
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -3,8 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
|
||||
def dema(candles: np.ndarray, period: int = 30, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
@@ -15,15 +14,16 @@ def dema(candles: np.ndarray, period: int = 30, source_type: str = "close", sequ
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 30
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
if len(candles.shape) == 1:
|
||||
source = candles
|
||||
else:
|
||||
candles = slice_candles(candles, sequential)
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
res = talib.DEMA(source, timeperiod=period)
|
||||
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -2,38 +2,44 @@ from typing import Union
|
||||
|
||||
import numpy as np
|
||||
import talib
|
||||
from jesse.indicators.mean_ad import mean_ad
|
||||
from jesse.indicators.median_ad import median_ad
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
def devstop(candles: np.ndarray, period:int=20, mult: float = 0, direction: str = "long", sequential: bool = False) -> Union[
|
||||
|
||||
def devstop(candles: np.ndarray, period: int = 20, mult: float = 0, devtype: int = 0, direction: str = "long",
|
||||
sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
Kase Dev Stops
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default=20
|
||||
:param mult: float - default=0
|
||||
:param direction: str - default=long
|
||||
:param sequential: bool - default=False
|
||||
:param period: int - default: 20
|
||||
:param mult: float - default: 0
|
||||
:param devtype: int - default: 0
|
||||
:param direction: str - default: long
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
high = candles[:, 3]
|
||||
low = candles[:, 4]
|
||||
|
||||
AVTR = talib.SMA(talib.MAX(high, 2) - talib.MIN(low, 2), period)
|
||||
SD = talib.STDDEV(talib.MAX(high, 2) - talib.MIN(low, 2), period)
|
||||
|
||||
if devtype == 0:
|
||||
SD = talib.STDDEV(talib.MAX(high, 2) - talib.MIN(low, 2), period)
|
||||
elif devtype == 1:
|
||||
SD = mean_ad(talib.MAX(high, 2) - talib.MIN(low, 2), period, sequential=True)
|
||||
elif devtype == 2:
|
||||
SD = median_ad(talib.MAX(high, 2) - talib.MIN(low, 2), period, sequential=True)
|
||||
|
||||
if direction == "long":
|
||||
res = talib.MAX(high - AVTR - mult * SD, period)
|
||||
else:
|
||||
res = talib.MIN(low + AVTR + mult * SD, period)
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -3,7 +3,7 @@ from collections import namedtuple
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
DI = namedtuple('DI', ['plus', 'minus'])
|
||||
|
||||
@@ -13,14 +13,12 @@ def di(candles: np.ndarray, period: int = 14, sequential: bool = False) -> DI:
|
||||
DI - Directional Indicator
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default=14
|
||||
:param sequential: bool - default=False
|
||||
:param period: int - default: 14
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: DI(plus, minus)
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
MINUS_DI = talib.MINUS_DI(candles[:, 3], candles[:, 4], candles[:, 2], timeperiod=period)
|
||||
PLUS_DI = talib.PLUS_DI(candles[:, 3], candles[:, 4], candles[:, 2], timeperiod=period)
|
||||
|
||||
@@ -3,7 +3,7 @@ from collections import namedtuple
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
DM = namedtuple('DM', ['plus', 'minus'])
|
||||
|
||||
@@ -13,14 +13,12 @@ def dm(candles: np.ndarray, period: int = 14, sequential: bool = False) -> DM:
|
||||
DM - Directional Movement
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default=14
|
||||
:param sequential: bool - default=False
|
||||
:param period: int - default: 14
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: DM(plus, minus)
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
MINUS_DI = talib.MINUS_DM(candles[:, 3], candles[:, 4], timeperiod=period)
|
||||
PLUS_DI = talib.PLUS_DM(candles[:, 3], candles[:, 4], timeperiod=period)
|
||||
|
||||
@@ -3,7 +3,7 @@ from collections import namedtuple
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
DonchianChannel = namedtuple('DonchianChannel', ['upperband', 'middleband', 'lowerband'])
|
||||
|
||||
@@ -14,13 +14,11 @@ def donchian(candles: np.ndarray, period: int = 20, sequential: bool = False) ->
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 20
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: DonchianChannel(upperband, middleband, lowerband)
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
UC = talib.MAX(candles[:, 3], timeperiod=period)
|
||||
LC = talib.MIN(candles[:, 4], timeperiod=period)
|
||||
|
||||
@@ -3,8 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
import tulipy as ti
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import get_candle_source, same_length, slice_candles
|
||||
|
||||
|
||||
def dpo(candles: np.ndarray, period: int = 5, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
@@ -15,15 +14,13 @@ def dpo(candles: np.ndarray, period: int = 5, source_type: str = "close", sequen
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 5
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
res = ti.dpo(np.ascontiguousarray(source), period=period)
|
||||
|
||||
return np.concatenate((np.full((candles.shape[0] - res.shape[0]), np.nan), res), axis=0) if sequential else res[-1]
|
||||
return same_length(candles, res) if sequential else res[-1]
|
||||
|
||||
@@ -4,7 +4,7 @@ import numpy as np
|
||||
import talib
|
||||
|
||||
import jesse.helpers as jh
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def dti(candles: np.ndarray, r: int = 14, s: int = 10, u: int = 5, sequential: bool = False) -> Union[
|
||||
@@ -13,16 +13,14 @@ def dti(candles: np.ndarray, r: int = 14, s: int = 10, u: int = 5, sequential: b
|
||||
DTI by William Blau
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param r: int - default=14
|
||||
:param s: int - default=10
|
||||
:param u: int - default=5
|
||||
:param sequential: bool - default=False
|
||||
:param r: int - default: 14
|
||||
:param s: int - default: 10
|
||||
:param u: int - default: 5
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
high = candles[:, 3]
|
||||
low = candles[:, 4]
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def dx(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
@@ -12,13 +12,11 @@ def dx(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Union
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 14
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
res = talib.DX(candles[:, 3], candles[:, 4], candles[:, 2], timeperiod=period)
|
||||
|
||||
|
||||
53
jesse/indicators/edcf.py
Normal file
53
jesse/indicators/edcf.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a: a
|
||||
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
|
||||
def edcf(candles: np.ndarray, period: int = 15, source_type: str = "hl2", sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
Ehlers Distance Coefficient Filter
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 15
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
|
||||
# Accept normal array too.
|
||||
if len(candles.shape) == 1:
|
||||
source = candles
|
||||
else:
|
||||
candles = slice_candles(candles, sequential)
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
res = edcf_fast(source, period)
|
||||
|
||||
return res if sequential else res[-1]
|
||||
|
||||
|
||||
@njit
|
||||
def edcf_fast(source, period):
|
||||
newseries = np.full_like(source, np.nan)
|
||||
|
||||
for j in range(2 * period, source.shape[0]):
|
||||
num = 0.0
|
||||
coefSum = 0.0
|
||||
for i in range(period):
|
||||
distance = 0.0
|
||||
for lb in range(1, period):
|
||||
distance += np.power(source[j - i] - source[j - i - lb], 2)
|
||||
num += distance * source[j - i]
|
||||
coefSum += distance
|
||||
newseries[j] = num / coefSum if coefSum != 0 else 0
|
||||
|
||||
return newseries
|
||||
@@ -2,10 +2,12 @@ from typing import Union
|
||||
|
||||
import numpy as np
|
||||
import talib
|
||||
from numba import njit
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import get_candle_source, same_length, slice_candles
|
||||
|
||||
|
||||
def efi(candles: np.ndarray, period: int = 13, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
@@ -16,27 +18,25 @@ def efi(candles: np.ndarray, period: int = 13, source_type: str = "close", seque
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 13
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
dif = efi_fast(source, candles[:, 5])
|
||||
|
||||
res = talib.EMA(dif, timeperiod=period)
|
||||
res_with_nan = np.concatenate((np.full((candles.shape[0] - res.shape[0]), np.nan), res))
|
||||
res_with_nan = same_length(candles, res)
|
||||
|
||||
return res_with_nan if sequential else res_with_nan[-1]
|
||||
|
||||
|
||||
@njit
|
||||
def efi_fast(source, volume):
|
||||
dif = np.zeros(len(source) - 1)
|
||||
for i in range(1, len(source)):
|
||||
dif = np.zeros(source.size - 1)
|
||||
for i in range(1, source.size):
|
||||
dif[i - 1] = (source[i] - source[i - 1]) * volume[i]
|
||||
return dif
|
||||
|
||||
@@ -3,8 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
|
||||
def ema(candles: np.ndarray, period: int = 5, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
@@ -15,15 +14,16 @@ def ema(candles: np.ndarray, period: int = 5, source_type: str = "close", sequen
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 5
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
if len(candles.shape) == 1:
|
||||
source = candles
|
||||
else:
|
||||
candles = slice_candles(candles, sequential)
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
res = talib.EMA(source, timeperiod=period)
|
||||
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -2,9 +2,12 @@ from collections import namedtuple
|
||||
|
||||
import numpy as np
|
||||
import talib
|
||||
from numba import njit
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
EMD = namedtuple('EMD', ['upperband', 'middleband', 'lowerband'])
|
||||
|
||||
@@ -14,16 +17,14 @@ def emd(candles: np.ndarray, period: int = 20, delta=0.5, fraction=0.1, sequenti
|
||||
Empirical Mode Decomposition by John F. Ehlers and Ric Way
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default=20
|
||||
:param delta: float - default=0.5
|
||||
:param fraction: float - default=0.1
|
||||
:param sequential: bool - default=False
|
||||
:param period: int - default: 20
|
||||
:param delta: float - default: 0.5
|
||||
:param fraction: float - default: 0.1
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: EMD(upperband, middleband, lowerband)
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
price = (candles[:, 3] + candles[:, 4]) / 2
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
import tulipy as ti
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles, same_length
|
||||
|
||||
|
||||
def emv(candles: np.ndarray, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
@@ -11,15 +11,13 @@ def emv(candles: np.ndarray, sequential: bool = False) -> Union[float, np.ndarra
|
||||
EMV - Ease of Movement
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
res = ti.emv(np.ascontiguousarray(candles[:, 3]), np.ascontiguousarray(candles[:, 4]),
|
||||
np.ascontiguousarray(candles[:, 5]))
|
||||
|
||||
return np.concatenate((np.full((candles.shape[0] - res.shape[0]), np.nan), res), axis=0) if sequential else res[-1]
|
||||
return same_length(candles, res) if sequential else res[-1]
|
||||
|
||||
50
jesse/indicators/epma.py
Normal file
50
jesse/indicators/epma.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from typing import Union
|
||||
import numpy as np
|
||||
|
||||
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
|
||||
def epma(candles: np.ndarray, period: int = 11, offset: int = 4, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
End Point Moving Average
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 14
|
||||
:param offset: int - default: 4
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
|
||||
# Accept normal array too.
|
||||
if len(candles.shape) == 1:
|
||||
source = candles
|
||||
else:
|
||||
candles = slice_candles(candles, sequential)
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
res = epma_fast(source, period, offset)
|
||||
|
||||
return res if sequential else res[-1]
|
||||
|
||||
|
||||
@njit
|
||||
def epma_fast(source, period, offset):
|
||||
newseries = np.copy(source)
|
||||
for j in range(period + offset + 1 , source.shape[0]):
|
||||
my_sum = 0.0
|
||||
weightSum = 0.0
|
||||
for i in range(period - 1):
|
||||
weight = period - i - offset
|
||||
my_sum += (source[j - i] * weight)
|
||||
weightSum += weight
|
||||
newseries[j] = 1 / weightSum * my_sum
|
||||
return newseries
|
||||
@@ -3,8 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
from numpy.lib.stride_tricks import sliding_window_view
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import get_candle_source, slice_candles, same_length
|
||||
|
||||
|
||||
def er(candles: np.ndarray, period: int = 5, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
@@ -15,13 +14,11 @@ def er(candles: np.ndarray, period: int = 5, source_type: str = "close", sequent
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 5
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
@@ -32,4 +29,4 @@ def er(candles: np.ndarray, period: int = 5, source_type: str = "close", sequent
|
||||
|
||||
res = change / volatility
|
||||
|
||||
return np.concatenate((np.full((candles.shape[0] - res.shape[0]), np.nan), res), axis=0) if sequential else res[-1]
|
||||
return same_length(candles, res) if sequential else res[-1]
|
||||
|
||||
35
jesse/indicators/eri.py
Normal file
35
jesse/indicators/eri.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from collections import namedtuple
|
||||
|
||||
import numpy as np
|
||||
from jesse.indicators.ma import ma
|
||||
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
ERI = namedtuple('ERI', ['bull', 'bear'])
|
||||
|
||||
|
||||
def eri(candles: np.ndarray, period: int = 13, matype: int = 1, source_type: str = "close",
|
||||
sequential: bool = False) -> ERI:
|
||||
"""
|
||||
Elder Ray Index (ERI)
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 13
|
||||
:param matype: int - default: 1
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
ema = ma(source, period=period, matype=matype, sequential=True)
|
||||
bull = candles[:, 3] - ema
|
||||
bear = candles[:, 4] - ema
|
||||
|
||||
if sequential:
|
||||
return ERI(bull, bear)
|
||||
else:
|
||||
return ERI(bull[-1], bear[-1])
|
||||
@@ -3,7 +3,7 @@ from collections import namedtuple
|
||||
import numpy as np
|
||||
import tulipy as ti
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles, same_length
|
||||
|
||||
FisherTransform = namedtuple('FisherTransform', ['fisher', 'signal'])
|
||||
|
||||
@@ -14,20 +14,16 @@ def fisher(candles: np.ndarray, period: int = 9, sequential: bool = False) -> Fi
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 9
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: FisherTransform(fisher, signal)
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
fisher, fisher_signal = ti.fisher(np.ascontiguousarray(candles[:, 3]), np.ascontiguousarray(candles[:, 4]),
|
||||
period=period)
|
||||
fisher_val, fisher_signal = ti.fisher(np.ascontiguousarray(candles[:, 3]), np.ascontiguousarray(candles[:, 4]),
|
||||
period=period)
|
||||
|
||||
if sequential:
|
||||
return FisherTransform(np.concatenate((np.full((candles.shape[0] - fisher.shape[0]), np.nan), fisher), axis=0),
|
||||
np.concatenate(
|
||||
(np.full((candles.shape[0] - fisher_signal.shape[0]), np.nan), fisher_signal)))
|
||||
return FisherTransform(same_length(candles, fisher_val), same_length(candles, fisher_signal))
|
||||
else:
|
||||
return FisherTransform(fisher[-1], fisher_signal[-1])
|
||||
return FisherTransform(fisher_val[-1], fisher_signal[-1])
|
||||
|
||||
@@ -3,8 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
import tulipy as ti
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import get_candle_source, same_length, slice_candles
|
||||
|
||||
|
||||
def fosc(candles: np.ndarray, period: int = 5, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
@@ -15,15 +14,13 @@ def fosc(candles: np.ndarray, period: int = 5, source_type: str = "close", seque
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 5
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
res = ti.fosc(np.ascontiguousarray(source), period=period)
|
||||
|
||||
return np.concatenate((np.full((candles.shape[0] - res.shape[0]), np.nan), res), axis=0) if sequential else res[-1]
|
||||
return same_length(candles, res) if sequential else res[-1]
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
from numba import njit
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def frama(candles: np.ndarray, window: int = 10, FC: int = 1, SC: int = 300, sequential: bool = False) -> Union[
|
||||
@@ -15,13 +18,11 @@ def frama(candles: np.ndarray, window: int = 10, FC: int = 1, SC: int = 300, seq
|
||||
:param window: int - default: 10
|
||||
:param FC: int - default: 1
|
||||
:param SC: int - default: 300
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
n = window
|
||||
|
||||
@@ -30,29 +31,29 @@ def frama(candles: np.ndarray, window: int = 10, FC: int = 1, SC: int = 300, seq
|
||||
print("FRAMA n must be even. Adding one")
|
||||
n += 1
|
||||
|
||||
frama = frame_fast(candles, n, SC, FC)
|
||||
res = frame_fast(candles, n, SC, FC)
|
||||
|
||||
if sequential:
|
||||
return frama
|
||||
return res
|
||||
else:
|
||||
return frama[-1]
|
||||
return res[-1]
|
||||
|
||||
|
||||
@njit
|
||||
def frame_fast(candles, n, SC, FC):
|
||||
w = np.log(2.0 / (SC + 1))
|
||||
|
||||
D = np.zeros(len(candles))
|
||||
D = np.zeros(candles.size)
|
||||
D[:n] = np.NaN
|
||||
|
||||
alphas = np.zeros(len(candles))
|
||||
alphas = np.zeros(candles.size)
|
||||
alphas[:n] = np.NaN
|
||||
|
||||
for i in range(n, len(candles)):
|
||||
for i in range(n, candles.shape[0]):
|
||||
per = candles[i - n:i]
|
||||
|
||||
v1 = per[len(per)//2:]
|
||||
v2 = per[:len(per)//2]
|
||||
v1 = per[per.shape[0] // 2:]
|
||||
v2 = per[:per.shape[0] // 2]
|
||||
|
||||
N1 = (max(v1[:, 3]) - min(v1[:, 4])) / (n / 2)
|
||||
N2 = (max(v2[:, 3]) - min(v2[:, 4])) / (n / 2)
|
||||
@@ -78,10 +79,10 @@ def frame_fast(candles, n, SC, FC):
|
||||
else:
|
||||
alphas[i] = alpha_
|
||||
|
||||
frama = np.zeros(len(candles))
|
||||
frama[n - 1] = np.mean(candles[:, 2][:n])
|
||||
frama[:n - 1] = np.NaN
|
||||
frama_val = np.zeros(candles.shape[0])
|
||||
frama_val[n - 1] = np.mean(candles[:, 2][:n])
|
||||
frama_val[:n - 1] = np.NaN
|
||||
|
||||
for i in range(n, len(frama)):
|
||||
frama[i] = (alphas[i] * candles[:, 2][i]) + (1 - alphas[i]) * frama[i - 1]
|
||||
return frama
|
||||
for i in range(n, frama_val.shape[0]):
|
||||
frama_val[i] = (alphas[i] * candles[:, 2][i]) + (1 - alphas[i]) * frama_val[i - 1]
|
||||
return frama_val
|
||||
|
||||
@@ -4,8 +4,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
from numpy.lib.stride_tricks import sliding_window_view
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import get_candle_source, slice_candles, same_length
|
||||
|
||||
|
||||
def fwma(candles: np.ndarray, period: int = 5, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
@@ -16,20 +15,23 @@ def fwma(candles: np.ndarray, period: int = 5, source_type: str = "close", seque
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 5
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
# Accept normal array too.
|
||||
if len(candles.shape) == 1:
|
||||
source = candles
|
||||
else:
|
||||
candles = slice_candles(candles, sequential)
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
fibs = fibonacci(n=period)
|
||||
swv = sliding_window_view(source, window_shape=period)
|
||||
res = np.average(swv, weights=fibs, axis=-1)
|
||||
|
||||
return np.concatenate((np.full((candles.shape[0] - res.shape[0]), np.nan), res), axis=0) if sequential else res[-1]
|
||||
return same_length(candles, res) if sequential else res[-1]
|
||||
|
||||
|
||||
def fibonacci(n: int = 2) -> np.ndarray:
|
||||
@@ -41,7 +43,7 @@ def fibonacci(n: int = 2) -> np.ndarray:
|
||||
|
||||
result = np.array([a])
|
||||
|
||||
for i in range(0, n):
|
||||
for _ in range(n):
|
||||
a, b = b, a + b
|
||||
result = np.append(result, a)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source, np_shift
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
GATOR = namedtuple('GATOR', ['upper', 'lower', 'upper_change', 'lower_change'])
|
||||
|
||||
@@ -15,14 +15,12 @@ def gatorosc(candles: np.ndarray, source_type: str = "close", sequential: bool =
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: GATOR(upper, lower, upper_change, lower_change)
|
||||
"""
|
||||
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
@@ -50,13 +48,11 @@ def numpy_ewma(data, window):
|
||||
:return:
|
||||
"""
|
||||
alpha = 1 / window
|
||||
scale = 1 / (1 - alpha)
|
||||
# scale = 1 / (1 - alpha)
|
||||
n = data.shape[0]
|
||||
scale_arr = (1 - alpha) ** (-1 * np.arange(n))
|
||||
weights = (1 - alpha) ** np.arange(n)
|
||||
pw0 = (1 - alpha) ** (n - 1)
|
||||
mult = data * pw0 * scale_arr
|
||||
cumsums = mult.cumsum()
|
||||
out = cumsums * scale_arr[::-1] / weights.cumsum()
|
||||
|
||||
return out
|
||||
return cumsums * scale_arr[::-1] / weights.cumsum()
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
from numba import njit
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
|
||||
def gauss(candles: np.ndarray, period: int = 14, poles: int = 4, source_type: str = "close",
|
||||
@@ -13,19 +15,20 @@ def gauss(candles: np.ndarray, period: int = 14, poles: int = 4, source_type: st
|
||||
Gaussian Filter
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default=14
|
||||
:param poles: int - default=4
|
||||
:param period: int - default: 14
|
||||
:param poles: int - default: 4
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
if len(candles.shape) == 1:
|
||||
source = candles
|
||||
else:
|
||||
candles = slice_candles(candles, sequential)
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
fil, to_fill = gauss_fast(source, period, poles)
|
||||
|
||||
if to_fill != 0:
|
||||
@@ -33,22 +36,19 @@ def gauss(candles: np.ndarray, period: int = 14, poles: int = 4, source_type: st
|
||||
else:
|
||||
res = fil[poles:]
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
return res if sequential else res[-1]
|
||||
|
||||
|
||||
@njit
|
||||
def gauss_fast(source, period, poles):
|
||||
N = len(source)
|
||||
N = source.size
|
||||
source = source[~np.isnan(source)]
|
||||
to_fill = N - len(source)
|
||||
to_fill = N - source.size
|
||||
PI = np.pi
|
||||
beta = (1 - np.cos(2 * PI / period)) / (np.power(2, 1 / poles) - 1)
|
||||
alpha = -beta + np.sqrt(np.power(beta, 2) + 2 * beta)
|
||||
|
||||
fil = np.zeros(poles + len(source))
|
||||
fil = np.zeros(poles + source.size)
|
||||
if poles == 1:
|
||||
coeff = np.array([alpha, (1 - alpha)])
|
||||
elif poles == 2:
|
||||
@@ -58,7 +58,7 @@ def gauss_fast(source, period, poles):
|
||||
elif poles == 4:
|
||||
coeff = np.array([alpha ** 4, 4 * (1 - alpha), -6 * (1 - alpha) ** 2, 4 * (1 - alpha) ** 3, -(1 - alpha) ** 4])
|
||||
|
||||
for i in range(len(source)):
|
||||
for i in range(source.size):
|
||||
if poles == 1:
|
||||
val = np.array([source[i].item(), fil[i]])
|
||||
elif poles == 2:
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
from numba import njit
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
|
||||
def high_pass(candles: np.ndarray, period: int = 48, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
@@ -13,18 +15,18 @@ def high_pass(candles: np.ndarray, period: int = 48, source_type: str = "close",
|
||||
(1 pole) high-pass filter indicator by John F. Ehlers
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default=48
|
||||
:param period: int - default: 48
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
if len(candles.shape) == 1:
|
||||
source = candles
|
||||
else:
|
||||
candles = slice_candles(candles, sequential)
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
hpf = high_pass_fast(source, period)
|
||||
|
||||
|
||||
@@ -1,30 +1,33 @@
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
from numba import njit
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
|
||||
def high_pass_2_pole(candles: np.ndarray, period: int = 48, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
def high_pass_2_pole(candles: np.ndarray, period: int = 48, source_type: str = "close", sequential: bool = False) -> \
|
||||
Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
(2 pole) high-pass filter indicator by John F. Ehlers
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default=48
|
||||
:param period: int - default: 48
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
if len(candles.shape) == 1:
|
||||
source = candles
|
||||
else:
|
||||
candles = slice_candles(candles, sequential)
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
hpf = high_pass_2_pole_fast(source, period)
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
import tulipy as ti
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import get_candle_source, same_length, slice_candles
|
||||
|
||||
|
||||
def hma(candles: np.ndarray, period: int = 5, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
@@ -15,15 +14,17 @@ def hma(candles: np.ndarray, period: int = 5, source_type: str = "close", sequen
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 5
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
# Accept normal array too.
|
||||
if len(candles.shape) == 1:
|
||||
source = candles
|
||||
else:
|
||||
candles = slice_candles(candles, sequential)
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
res = ti.hma(np.ascontiguousarray(source), period=period)
|
||||
|
||||
return np.concatenate((np.full((candles.shape[0] - res.shape[0]), np.nan), res), axis=0) if sequential else res[-1]
|
||||
return same_length(candles, res) if sequential else res[-1]
|
||||
|
||||
@@ -4,7 +4,7 @@ import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def ht_dcperiod(candles: np.ndarray, source_type: str = "close", sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
@@ -13,13 +13,11 @@ def ht_dcperiod(candles: np.ndarray, source_type: str = "close", sequential: boo
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
res = talib.HT_DCPERIOD(source)
|
||||
|
||||
@@ -4,7 +4,7 @@ import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def ht_dcphase(candles: np.ndarray, source_type: str = "close", sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
@@ -13,13 +13,11 @@ def ht_dcphase(candles: np.ndarray, source_type: str = "close", sequential: bool
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
res = talib.HT_DCPHASE(source)
|
||||
|
||||
@@ -4,7 +4,7 @@ import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
IQ = namedtuple('IQ', ['inphase', 'quadrature'])
|
||||
|
||||
@@ -15,13 +15,11 @@ def ht_phasor(candles: np.ndarray, source_type: str = "close", sequential: bool
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: IQ(inphase, quadrature)
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
inphase, quadrature = talib.HT_PHASOR(source)
|
||||
|
||||
@@ -4,7 +4,7 @@ import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
SINEWAVE = namedtuple('SINEWAVE', ['sine', 'lead'])
|
||||
|
||||
@@ -15,13 +15,11 @@ def ht_sine(candles: np.ndarray, source_type: str = "close", sequential: bool =
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: SINEWAVE(sine, lead)
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
sine, leadsine = talib.HT_SINE(source)
|
||||
|
||||
@@ -4,7 +4,7 @@ import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def ht_trendline(candles: np.ndarray, source_type: str = "close", sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
@@ -13,15 +13,16 @@ def ht_trendline(candles: np.ndarray, source_type: str = "close", sequential: bo
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
if len(candles.shape) == 1:
|
||||
source = candles
|
||||
else:
|
||||
candles = slice_candles(candles, sequential)
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
res = talib.HT_TRENDLINE(source)
|
||||
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -4,7 +4,7 @@ import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def ht_trendmode(candles: np.ndarray, source_type: str = "close", sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
@@ -13,13 +13,11 @@ def ht_trendmode(candles: np.ndarray, source_type: str = "close", sequential: bo
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: int | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
res = talib.HT_TRENDMODE(source)
|
||||
|
||||
209
jesse/indicators/hurst_exponent.py
Normal file
209
jesse/indicators/hurst_exponent.py
Normal file
@@ -0,0 +1,209 @@
|
||||
import numpy as np
|
||||
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
from scipy import signal
|
||||
|
||||
def hurst_exponent(candles: np.ndarray, min_chunksize: int = 8, max_chunksize: int = 200, num_chunksize:int=5, method:int=1, source_type: str = "close") -> float:
|
||||
"""
|
||||
Hurst Exponent
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param min_chunksize: int - default: 8
|
||||
:param max_chunksize: int - default: 200
|
||||
:param num_chunksize: int - default: 5
|
||||
:param method: int - default: 1 - 0: RS | 1: DMA | 2: DSOD
|
||||
:param source_type: str - default: "close"
|
||||
|
||||
:return: float
|
||||
"""
|
||||
|
||||
if len(candles.shape) == 1:
|
||||
source = candles
|
||||
else:
|
||||
candles = slice_candles(candles, False)
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
if method == 0:
|
||||
h = hurst_rs(np.diff(source), min_chunksize, max_chunksize, num_chunksize)
|
||||
elif method == 1:
|
||||
h = hurst_dma(source, min_chunksize, max_chunksize, num_chunksize)
|
||||
elif method == 2:
|
||||
h = hurst_dsod(source)
|
||||
else:
|
||||
raise NotImplementedError('The method choose is not implemented.')
|
||||
|
||||
return None if np.isnan(h) else h
|
||||
|
||||
|
||||
@njit
|
||||
def hurst_rs(x, min_chunksize, max_chunksize, num_chunksize):
|
||||
"""Estimate the Hurst exponent using R/S method.
|
||||
Estimates the Hurst (H) exponent using the R/S method from the time series.
|
||||
The R/S method consists of dividing the series into pieces of equal size
|
||||
`series_len` and calculating the rescaled range. This repeats the process
|
||||
for several `series_len` values and adjusts data regression to obtain the H.
|
||||
`series_len` will take values between `min_chunksize` and `max_chunksize`,
|
||||
the step size from `min_chunksize` to `max_chunksize` can be controlled
|
||||
through the parameter `step_chunksize`.
|
||||
Parameters
|
||||
----------
|
||||
x : 1D-array
|
||||
A time series to calculate hurst exponent, must have more elements
|
||||
than `min_chunksize` and `max_chunksize`.
|
||||
min_chunksize : int
|
||||
This parameter allow you control the minimum window size.
|
||||
max_chunksize : int
|
||||
This parameter allow you control the maximum window size.
|
||||
num_chunksize : int
|
||||
This parameter allow you control the size of the step from minimum to
|
||||
maximum window size. Bigger step means fewer calculations.
|
||||
out : 1-element-array, optional
|
||||
one element array to store the output.
|
||||
Returns
|
||||
-------
|
||||
H : float
|
||||
A estimation of Hurst exponent.
|
||||
References
|
||||
----------
|
||||
Hurst, H. E. (1951). Long term storage capacity of reservoirs. ASCE
|
||||
Transactions, 116(776), 770-808.
|
||||
Alessio, E., Carbone, A., Castelli, G. et al. Eur. Phys. J. B (2002) 27:
|
||||
197. http://dx.doi.org/10.1140/epjb/e20020150
|
||||
"""
|
||||
N = len(x)
|
||||
max_chunksize += 1
|
||||
rs_tmp = np.empty(N, dtype=np.float64)
|
||||
chunk_size_list = np.linspace(min_chunksize, max_chunksize, num_chunksize) \
|
||||
.astype(np.int64)
|
||||
rs_values_list = np.empty(num_chunksize, dtype=np.float64)
|
||||
|
||||
# 1. The series is divided into chunks of chunk_size_list size
|
||||
for i in range(num_chunksize):
|
||||
chunk_size = chunk_size_list[i]
|
||||
|
||||
# 2. it iterates on the indices of the first observation of each chunk
|
||||
number_of_chunks = int(len(x) / chunk_size)
|
||||
|
||||
for idx in range(number_of_chunks):
|
||||
# next means no overlapping
|
||||
# convert index to index selection of each chunk
|
||||
ini = idx * chunk_size
|
||||
end = ini + chunk_size
|
||||
chunk = x[ini:end]
|
||||
|
||||
# 2.1 Calculate the RS (chunk_size)
|
||||
z = np.cumsum(chunk - np.mean(chunk))
|
||||
rs_tmp[idx] = np.divide(
|
||||
np.max(z) - np.min(z), # range
|
||||
np.nanstd(chunk) # standar deviation
|
||||
)
|
||||
|
||||
# 3. Average of RS(chunk_size)
|
||||
rs_values_list[i] = np.nanmean(rs_tmp[:idx + 1])
|
||||
|
||||
# 4. calculate the Hurst exponent.
|
||||
H, c = np.linalg.lstsq(
|
||||
a=np.vstack((np.log(chunk_size_list), np.ones(num_chunksize))).T,
|
||||
b=np.log(rs_values_list)
|
||||
)[0]
|
||||
|
||||
return H
|
||||
|
||||
def hurst_dma(prices, min_chunksize=8, max_chunksize=200, num_chunksize=5):
|
||||
"""Estimate the Hurst exponent using R/S method.
|
||||
|
||||
Estimates the Hurst (H) exponent using the DMA method from the time series.
|
||||
The DMA method consists on calculate the moving average of size `series_len`
|
||||
and subtract it to the original series and calculating the standard
|
||||
deviation of that result. This repeats the process for several `series_len`
|
||||
values and adjusts data regression to obtain the H. `series_len` will take
|
||||
values between `min_chunksize` and `max_chunksize`, the step size from
|
||||
`min_chunksize` to `max_chunksize` can be controlled through the parameter
|
||||
`step_chunksize`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
prices
|
||||
min_chunksize
|
||||
max_chunksize
|
||||
num_chunksize
|
||||
|
||||
Returns
|
||||
-------
|
||||
hurst_exponent : float
|
||||
Estimation of hurst exponent.
|
||||
|
||||
References
|
||||
----------
|
||||
Alessio, E., Carbone, A., Castelli, G. et al. Eur. Phys. J. B (2002) 27:
|
||||
197. https://dx.doi.org/10.1140/epjb/e20020150
|
||||
|
||||
"""
|
||||
max_chunksize += 1
|
||||
N = len(prices)
|
||||
n_list = np.arange(min_chunksize, max_chunksize, num_chunksize, dtype=np.int64)
|
||||
dma_list = np.empty(len(n_list))
|
||||
factor = 1 / (N - max_chunksize)
|
||||
# sweeping n_list
|
||||
for i, n in enumerate(n_list):
|
||||
b = np.divide([n - 1] + (n - 1) * [-1], n) # do the same as: y - y_ma_n
|
||||
noise = np.power(signal.lfilter(b, 1, prices)[max_chunksize:], 2)
|
||||
dma_list[i] = np.sqrt(factor * np.sum(noise))
|
||||
|
||||
H, const = np.linalg.lstsq(
|
||||
a=np.vstack([np.log10(n_list), np.ones(len(n_list))]).T,
|
||||
b=np.log10(dma_list), rcond=None
|
||||
)[0]
|
||||
return H
|
||||
|
||||
|
||||
def hurst_dsod(x):
|
||||
"""Estimate Hurst exponent on data timeseries.
|
||||
|
||||
The estimation is based on the discrete second order derivative. Consists on
|
||||
get two different noise of the original series and calculate the standard
|
||||
deviation and calculate the slope of two point with that values.
|
||||
source: https://gist.github.com/wmvanvliet/d883c3fe1402c7ced6fc
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : numpy array
|
||||
time series to estimate the Hurst exponent for.
|
||||
|
||||
Returns
|
||||
-------
|
||||
h : float
|
||||
The estimation of the Hurst exponent for the given time series.
|
||||
|
||||
References
|
||||
----------
|
||||
Istas, J.; G. Lang (1994), “Quadratic variations and estimation of the local
|
||||
Hölder index of data Gaussian process,” Ann. Inst. Poincaré, 33, pp. 407–436.
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
This hurst_ets is data literal traduction of wfbmesti.m of waveleet toolbox
|
||||
from matlab.
|
||||
"""
|
||||
y = np.cumsum(np.diff(x, axis=0), axis=0)
|
||||
|
||||
# second order derivative
|
||||
b1 = [1, -2, 1]
|
||||
y1 = signal.lfilter(b1, 1, y, axis=0)
|
||||
y1 = y1[len(b1) - 1:] # first values contain filter artifacts
|
||||
|
||||
# wider second order derivative
|
||||
b2 = [1, 0, -2, 0, 1]
|
||||
y2 = signal.lfilter(b2, 1, y, axis=0)
|
||||
y2 = y2[len(b2) - 1:] # first values contain filter artifacts
|
||||
|
||||
s1 = np.mean(y1 ** 2, axis=0)
|
||||
s2 = np.mean(y2 ** 2, axis=0)
|
||||
|
||||
return 0.5 * np.log2(s2 / s1)
|
||||
54
jesse/indicators/hwma.py
Normal file
54
jesse/indicators/hwma.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import get_candle_source, slice_candles, same_length
|
||||
|
||||
|
||||
def hwma(candles: np.ndarray, na: float = 0.2, nb: float = 0.1, nc: float = 0.1, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
Holt-Winter Moving Average
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param na: float - default: 0.2
|
||||
:param nb: float - default: 0.1
|
||||
:param nc: float - default: 0.1
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
if not ((0 < na < 1) or (0 < nb < 1) or (0 < nc < 1)):
|
||||
raise ValueError("Bad parameters. They have to be: 0 < na nb nc < 1")
|
||||
|
||||
# Accept normal array too.
|
||||
if len(candles.shape) == 1:
|
||||
source = candles
|
||||
else:
|
||||
candles = slice_candles(candles, sequential)
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
source_without_nan = source[~np.isnan(source)]
|
||||
res = hwma_fast(source_without_nan, na, nb, nc)
|
||||
res = same_length(candles, res)
|
||||
|
||||
return res if sequential else res[-1]
|
||||
|
||||
|
||||
@njit
|
||||
def hwma_fast(source, na, nb, nc):
|
||||
last_a = last_v = 0
|
||||
last_f = source[0]
|
||||
newseries = np.copy(source)
|
||||
for i in range(source.size):
|
||||
F = (1.0 - na) * (last_f + last_v + 0.5 * last_a) + na * source[i]
|
||||
V = (1.0 - nb) * (last_v + last_a) + nb * (F - last_f)
|
||||
A = (1.0 - nc) * last_a + nc * (V - last_v)
|
||||
newseries[i] = F + V + 0.5 * A
|
||||
last_a, last_f, last_v = A, F, V # update values
|
||||
return newseries
|
||||
@@ -11,17 +11,17 @@ def ichimoku_cloud(candles: np.ndarray, conversion_line_period: int = 9, base_li
|
||||
Ichimoku Cloud
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param conversion_line_period: int - default=9
|
||||
:param base_line_period: int - default=26
|
||||
:param lagging_line_period: int - default=52
|
||||
:param displacement: - default=26
|
||||
:param conversion_line_period: int - default: 9
|
||||
:param base_line_period: int - default: 26
|
||||
:param lagging_line_period: int - default: 52
|
||||
:param displacement: - default: 26
|
||||
|
||||
:return: IchimokuCloud(conversion_line, base_line, span_a, span_b)
|
||||
"""
|
||||
if len(candles) < 80:
|
||||
if candles.shape[0] < 80:
|
||||
return IchimokuCloud(np.nan, np.nan, np.nan, np.nan)
|
||||
|
||||
if len(candles) > 80:
|
||||
if candles.shape[0] > 80:
|
||||
candles = candles[-80:]
|
||||
|
||||
# earlier
|
||||
|
||||
@@ -3,8 +3,8 @@ from collections import namedtuple
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import np_shift
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
IchimokuCloud = namedtuple('IchimokuCloud',
|
||||
['conversion_line', 'base_line', 'span_a', 'span_b', 'lagging_line', 'future_span_a',
|
||||
@@ -18,37 +18,26 @@ def ichimoku_cloud_seq(candles: np.ndarray, conversion_line_period: int = 9, bas
|
||||
Ichimoku Cloud
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param conversion_line_period: int - default=9
|
||||
:param base_line_period: int - default=26
|
||||
:param lagging_line_period: int - default=52
|
||||
:param displacement: - default=26
|
||||
:param sequential: bool - default=False
|
||||
:param conversion_line_period: int - default: 9
|
||||
:param base_line_period: int - default: 26
|
||||
:param lagging_line_period: int - default: 52
|
||||
:param displacement: - default: 26
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: IchimokuCloud
|
||||
"""
|
||||
|
||||
if len(candles) < lagging_line_period + displacement:
|
||||
if candles.shape[0] < lagging_line_period + displacement:
|
||||
raise ValueError("Too few candles available for lagging_line_period + displacement.")
|
||||
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
small_ph = talib.MAX(candles[:, 3], conversion_line_period)
|
||||
small_pl = talib.MIN(candles[:, 4], conversion_line_period)
|
||||
conversion_line = (small_ph + small_pl) / 2
|
||||
|
||||
mid_ph = talib.MAX(candles[:, 3], base_line_period)
|
||||
mid_pl = talib.MIN(candles[:, 4], base_line_period)
|
||||
base_line = (mid_ph + mid_pl) / 2
|
||||
|
||||
long_ph = talib.MAX(candles[:, 3], lagging_line_period)
|
||||
long_pl = talib.MIN(candles[:, 4], lagging_line_period)
|
||||
span_b_pre = (long_ph + long_pl) / 2
|
||||
conversion_line = _line_helper(candles, conversion_line_period)
|
||||
base_line = _line_helper(candles, base_line_period)
|
||||
span_b_pre = _line_helper(candles, lagging_line_period)
|
||||
span_b = np_shift(span_b_pre, displacement, fill_value=np.nan)
|
||||
span_a_pre = (conversion_line + base_line) / 2
|
||||
span_a = np_shift(span_a_pre, displacement, fill_value=np.nan)
|
||||
|
||||
lagging_line = np_shift(candles[:, 2], displacement - 1, fill_value=np.nan)
|
||||
|
||||
if sequential:
|
||||
@@ -56,3 +45,8 @@ def ichimoku_cloud_seq(candles: np.ndarray, conversion_line_period: int = 9, bas
|
||||
else:
|
||||
return IchimokuCloud(conversion_line[-1], base_line[-1], span_a[-1], span_b[-1], lagging_line[-1],
|
||||
span_a_pre[-1], span_b_pre[-1])
|
||||
|
||||
def _line_helper(candles, period):
|
||||
small_ph = talib.MAX(candles[:, 3], period)
|
||||
small_pl = talib.MIN(candles[:, 4], period)
|
||||
return (small_ph + small_pl) / 2
|
||||
|
||||
36
jesse/indicators/ift_rsi.py
Normal file
36
jesse/indicators/ift_rsi.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from typing import Union
|
||||
import talib
|
||||
|
||||
import numpy as np
|
||||
|
||||
from jesse.helpers import get_candle_source, slice_candles, same_length
|
||||
|
||||
|
||||
def ift_rsi(candles: np.ndarray, rsi_period: int = 5, wma_period: int =9, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
Modified Inverse Fisher Transform applied on RSI
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param rsi_period: int - default: 5
|
||||
:param wma_period: int - default: 9
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
|
||||
|
||||
candles = slice_candles(candles, sequential)
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
v1 = 0.1 * (talib.RSI(source, rsi_period) - 50)
|
||||
|
||||
v2 = talib.WMA(v1, wma_period)
|
||||
|
||||
res = (((2*v2) ** 2 - 1) / ((2*v2) ** 2 + 1))
|
||||
|
||||
return same_length(candles, res) if sequential else res[-1]
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from collections import namedtuple
|
||||
|
||||
import numpy as np
|
||||
from numba import njit
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
ITREND = namedtuple('ITREND', ['signal', 'it', 'trigger'])
|
||||
|
||||
@@ -16,13 +18,11 @@ def itrend(candles: np.ndarray, alpha: float = 0.07, source_type: str = "hl2", s
|
||||
:param candles: np.ndarray
|
||||
:param alpha: float - default: 0.07
|
||||
:param source_type: str - default: "hl2"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: ITREND(signal, it, trigger)
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
|
||||
48
jesse/indicators/jma.py
Normal file
48
jesse/indicators/jma.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
|
||||
def jma(candles: np.ndarray, period:int=7, phase:float=50, power:int=2, source_type:str='close', sequential:bool=False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
Jurik Moving Average
|
||||
Port of: https://tradingview.com/script/nZuBWW9j-Jurik-Moving-Average/
|
||||
"""
|
||||
|
||||
if len(candles.shape) == 1:
|
||||
source = candles
|
||||
else:
|
||||
candles = slice_candles(candles, sequential)
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
phaseRatio = 0.5 if phase < -100 else (2.5 if phase > 100 else phase / 100 + 1.5)
|
||||
beta = 0.45 * (period - 1) / (0.45 * (period - 1) + 2)
|
||||
alpha = pow(beta, power)
|
||||
|
||||
res = jma_helper(source, phaseRatio, beta, alpha)
|
||||
|
||||
return res if sequential else res[-1]
|
||||
|
||||
|
||||
@njit
|
||||
def jma_helper(src, phaseRatio, beta, alpha):
|
||||
jma_val = np.copy(src)
|
||||
|
||||
e0 = np.full_like(src, 0)
|
||||
e1 = np.full_like(src, 0)
|
||||
e2 = np.full_like(src, 0)
|
||||
|
||||
for i in range(1, src.shape[0]):
|
||||
e0[i] = (1 - alpha) * src[i] + alpha * e0[i-1]
|
||||
e1[i] = (src[i] - e0[i]) * (1 - beta) + beta * e1[i-1]
|
||||
e2[i] = (e0[i] + phaseRatio * e1[i] - jma_val[i - 1]) * pow(1 - alpha, 2) + pow(alpha, 2) * e2[i - 1]
|
||||
jma_val[i] = e2[i] + jma_val[i - 1]
|
||||
|
||||
return jma_val
|
||||
28
jesse/indicators/jsa.py
Normal file
28
jesse/indicators/jsa.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
|
||||
from jesse.helpers import get_candle_source, slice_candles, np_shift
|
||||
|
||||
|
||||
def jsa(candles: np.ndarray, period: int = 30, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
Jsa Moving Average
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 30
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
if len(candles.shape) == 1:
|
||||
source = candles
|
||||
else:
|
||||
candles = slice_candles(candles, sequential)
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
res = (source + np_shift(source, period, np.nan)) / 2
|
||||
|
||||
return res if sequential else res[-1]
|
||||
@@ -4,7 +4,7 @@ import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def kama(candles: np.ndarray, period: int = 30, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
@@ -15,15 +15,16 @@ def kama(candles: np.ndarray, period: int = 30, source_type: str = "close", sequ
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 30
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
if len(candles.shape) == 1:
|
||||
source = candles
|
||||
else:
|
||||
candles = slice_candles(candles, sequential)
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
res = talib.KAMA(source, timeperiod=period)
|
||||
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -1,39 +1,32 @@
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_config
|
||||
from jesse.indicators.ma import ma
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def kaufmanstop(candles: np.ndarray, period: int = 22, mult: float = 2, direction: str = "long", sequential: bool = False) -> Union[
|
||||
def kaufmanstop(candles: np.ndarray, period: int = 22, mult: float = 2, direction: str = "long", matype: int = 0,
|
||||
sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
Perry Kaufman's Stops
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default=22
|
||||
:param mult: float - default=2
|
||||
:param direction: str - default=long
|
||||
:param sequential: bool - default=False
|
||||
:param period: int - default: 22
|
||||
:param mult: float - default: 2
|
||||
:param direction: str - default: long
|
||||
:param matype: int - default: 0
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
high = candles[:, 3]
|
||||
low = candles[:, 4]
|
||||
|
||||
hl_diff = talib.SMA(high - low, period)
|
||||
hl_diff = ma(high - low, period=period, matype=matype, sequential=True)
|
||||
|
||||
if direction == "long":
|
||||
res = hl_diff * mult - low
|
||||
else:
|
||||
res = hl_diff * mult + high
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
res = low - hl_diff * mult if direction == "long" else high + hl_diff * mult
|
||||
return res if sequential else res[-1]
|
||||
|
||||
43
jesse/indicators/kdj.py
Normal file
43
jesse/indicators/kdj.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from collections import namedtuple
|
||||
|
||||
import numpy as np
|
||||
import talib
|
||||
from jesse.indicators.ma import ma
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
KDJ = namedtuple('KDJ', ['k', 'd', 'j'])
|
||||
|
||||
def kdj(candles: np.ndarray, fastk_period: int = 9, slowk_period: int = 3, slowk_matype: int = 0,
|
||||
slowd_period: int = 3, slowd_matype: int = 0, sequential: bool = False) -> KDJ:
|
||||
"""
|
||||
The KDJ Oscillator
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param fastk_period: int - default: 9
|
||||
:param slowk_period: int - default: 3
|
||||
:param slowk_matype: int - default: 0
|
||||
:param slowd_period: int - default: 3
|
||||
:param slowd_matype: int - default: 0
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: KDJ(k, d, j)
|
||||
"""
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
candles_close = candles[:, 2]
|
||||
candles_high = candles[:, 3]
|
||||
candles_low = candles[:, 4]
|
||||
|
||||
hh = talib.MAX(candles_high, fastk_period)
|
||||
ll = talib.MIN(candles_low, fastk_period)
|
||||
|
||||
stoch = 100 * (candles_close - ll) / (hh - ll)
|
||||
k = ma(stoch, period=slowk_period, matype=slowk_matype, sequential=True)
|
||||
d = ma(k, period=slowd_period, matype=slowd_matype, sequential=True)
|
||||
j = 3 * k - 2 * d
|
||||
|
||||
if sequential:
|
||||
return KDJ(k, d, j)
|
||||
else:
|
||||
return KDJ(k[-1], d[-1], j[-1])
|
||||
@@ -3,8 +3,10 @@ from collections import namedtuple
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.indicators.ma import ma
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_config
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
KeltnerChannel = namedtuple('KeltnerChannel', ['upperband', 'middleband', 'lowerband'])
|
||||
|
||||
@@ -16,20 +18,18 @@ def keltner(candles: np.ndarray, period: int = 20, multiplier: float = 2, matype
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 20
|
||||
:param multiplier: int - default: 2
|
||||
:param multiplier: float - default: 2
|
||||
:param matype: int - default: 1
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: KeltnerChannel(upperband, middleband, lowerband)
|
||||
"""
|
||||
|
||||
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
|
||||
if not sequential and len(candles) > warmup_candles_num:
|
||||
candles = candles[-warmup_candles_num:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
e = talib.MA(source, timeperiod=period, matype=matype)
|
||||
e = ma(source, period=period, matype=matype, sequential=True)
|
||||
a = talib.ATR(candles[:, 3], candles[:, 4], candles[:, 2], timeperiod=period)
|
||||
|
||||
up = e + a * multiplier
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user