Compare commits
1146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92269df6b8 | ||
|
|
25f1076629 | ||
|
|
2f45ddfca5 | ||
|
|
bafb17684d | ||
|
|
5b42f88531 | ||
|
|
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 | ||
|
|
816bdf0aea | ||
|
|
f44a07f3b8 | ||
|
|
34001e860d | ||
|
|
ff4f2f8ac2 | ||
|
|
5b7996adda | ||
|
|
21925e5f23 | ||
|
|
60aaeedda0 | ||
|
|
3d10b5d5a2 | ||
|
|
60238aea43 | ||
|
|
e97b88df45 | ||
|
|
043666b96f | ||
|
|
cff6b63ae5 | ||
|
|
5fd0bf5383 | ||
|
|
f93aebbc81 | ||
|
|
797a914e4e | ||
|
|
d995163498 | ||
|
|
5c2f0b6c38 | ||
|
|
328c16ca69 | ||
|
|
ccd231b51c | ||
|
|
a4daf13513 | ||
|
|
5f025cc72a | ||
|
|
d76027fba6 | ||
|
|
dffdf15cfa | ||
|
|
61e679acfd | ||
|
|
58df1a1499 | ||
|
|
51dcea96eb | ||
|
|
0bc6f94b58 | ||
|
|
4a01f536df | ||
|
|
14e0a73ce4 | ||
|
|
4820ed2ddd | ||
|
|
8af63a0f3a | ||
|
|
95023593bc | ||
|
|
b30a5e2de0 | ||
|
|
a2b39c303d | ||
|
|
1df4dbc223 | ||
|
|
6454cb2255 | ||
|
|
76d1ee029b | ||
|
|
a3bf9631b6 | ||
|
|
4b0acbdcba | ||
|
|
b5547b4768 | ||
|
|
2795ebaeb5 | ||
|
|
6f2e17d05a | ||
|
|
157b161b83 | ||
|
|
3e5b8ae508 | ||
|
|
a5692e2ab5 | ||
|
|
b4624fcf96 | ||
|
|
51d35e43c3 | ||
|
|
31b4f1dc3c | ||
|
|
8e79331ffb | ||
|
|
85ef8cf9d5 | ||
|
|
0b19b16f9e | ||
|
|
cc4c3207eb | ||
|
|
7b0951f083 | ||
|
|
77b71d8164 | ||
|
|
94b74b834d | ||
|
|
e004ff7f4a | ||
|
|
0858bbac3b | ||
|
|
f4069a14c0 | ||
|
|
4b9edda1ec | ||
|
|
bc303a04e9 | ||
|
|
831130c605 | ||
|
|
9eee3e70d7 | ||
|
|
4f3bcabfe6 | ||
|
|
9c080b4bab | ||
|
|
0392cac894 | ||
|
|
a42fc9d9ab | ||
|
|
21b76aa8c8 | ||
|
|
580b91db21 | ||
|
|
4a5162aaaf | ||
|
|
508bd0acde | ||
|
|
5d93e8b0d6 | ||
|
|
46f7ef1973 | ||
|
|
18d609b08d | ||
|
|
1cfcd8bbb0 | ||
|
|
8a17c2fb15 | ||
|
|
51e48396b0 | ||
|
|
8dcc0f17a1 | ||
|
|
bc86d82bb8 | ||
|
|
f1b46a603d | ||
|
|
42bf7baf8d | ||
|
|
04fdfb390c | ||
|
|
8efc66c13f | ||
|
|
689da58d4b | ||
|
|
c926772354 | ||
|
|
7971bafbd3 | ||
|
|
963f6df397 | ||
|
|
7a0b60fc44 | ||
|
|
650652ea66 | ||
|
|
e00b5c0b1a | ||
|
|
97e750c9d5 | ||
|
|
bebdab0348 | ||
|
|
ea73c4afe9 | ||
|
|
1712c6162e | ||
|
|
8b4f426b36 | ||
|
|
1a07afa365 | ||
|
|
5d6f0d170d | ||
|
|
d270217719 | ||
|
|
2e078016b2 | ||
|
|
eb53f182ba | ||
|
|
1f4424c6d9 | ||
|
|
109680eaf2 | ||
|
|
0f96bcc507 | ||
|
|
336875b147 | ||
|
|
3ad0a2bee5 | ||
|
|
07db5c7bd5 | ||
|
|
ceb0331907 | ||
|
|
e39bccbe7d | ||
|
|
6ae50534a3 | ||
|
|
95849bd482 | ||
|
|
13a7fea49d | ||
|
|
8bde520ffc | ||
|
|
d8dc65282f | ||
|
|
4681221bac | ||
|
|
22732f1db8 | ||
|
|
df82b85a9b | ||
|
|
52640815ed | ||
|
|
00b3fccb9e | ||
|
|
5d74df9eed | ||
|
|
f9917d6a86 | ||
|
|
60f893d39d | ||
|
|
284d54e823 | ||
|
|
a654af4899 | ||
|
|
97b5d5275d | ||
|
|
2a0b67d3cf | ||
|
|
e5cc608b55 | ||
|
|
e15fee22ee | ||
|
|
3219e11c76 | ||
|
|
bd4d50daca | ||
|
|
bc2efcbab3 | ||
|
|
27ce01ce2c | ||
|
|
76a1b9bf8b | ||
|
|
76cc671d13 | ||
|
|
4826d3f96a | ||
|
|
dd68a02bc7 | ||
|
|
b006ef5f4e | ||
|
|
3fbffae598 | ||
|
|
d89e46d136 | ||
|
|
981ccf1c71 | ||
|
|
746a220ef6 | ||
|
|
d93c4e2916 | ||
|
|
edc2817b3d | ||
|
|
a12033c7a0 | ||
|
|
fd955a432c | ||
|
|
077201ddcc | ||
|
|
5034b6b9fa | ||
|
|
340ca60ebe | ||
|
|
c5e8d5d6e6 | ||
|
|
08d0b99a4d | ||
|
|
df11855fe4 | ||
|
|
7afe5eb3c4 | ||
|
|
cf4b681bb3 | ||
|
|
83008ab01e | ||
|
|
dcce864e6d | ||
|
|
422d813dd8 | ||
|
|
fd2812b1b5 | ||
|
|
d6b1eb174b | ||
|
|
1af5e7c1b1 | ||
|
|
b801cc09e1 | ||
|
|
9f7b89f770 | ||
|
|
d60e00a396 | ||
|
|
6304fdab05 | ||
|
|
dc2bc2d917 | ||
|
|
83f537e099 | ||
|
|
4019565091 | ||
|
|
c095b2bb68 | ||
|
|
9a38ffa7e5 | ||
|
|
6a229cc3cb | ||
|
|
1fd01b7390 | ||
|
|
f6b34fd3f5 | ||
|
|
7466f80730 | ||
|
|
edeaf96e0b | ||
|
|
49e23a036c | ||
|
|
81f51107b9 | ||
|
|
cdb1912e24 | ||
|
|
699a68045b | ||
|
|
18fee9f5d0 | ||
|
|
b8473ae6cb | ||
|
|
2e91359a81 | ||
|
|
e6e375961a | ||
|
|
96406afb8d | ||
|
|
63015ef81e | ||
|
|
89e9e0e097 | ||
|
|
8777ed36f6 | ||
|
|
49a1ac4671 | ||
|
|
220c545b41 | ||
|
|
1241c04e8e | ||
|
|
7798ba1db2 | ||
|
|
5b65ea3506 | ||
|
|
17083487e7 | ||
|
|
d6c2a96270 | ||
|
|
ef31851558 | ||
|
|
e919a3a68b |
25
.dockerignore
Normal file
25
.dockerignore
Normal file
@@ -0,0 +1,25 @@
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.Python
|
||||
env
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
.tox
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
*.log
|
||||
.git
|
||||
*.md
|
||||
!README*.md
|
||||
README-secret.md
|
||||
.travis.yml
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
.idea
|
||||
venv
|
||||
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.
|
||||
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
||||
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
|
||||
157
.gitignore
vendored
157
.gitignore
vendored
@@ -1,20 +1,151 @@
|
||||
/_pycache_
|
||||
/.pytest_cache
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# IDE
|
||||
/.vscode
|
||||
/jesse.egg-info
|
||||
/.idea
|
||||
|
||||
.DS_Store
|
||||
/storage/*.key
|
||||
/storage/*.sqlite
|
||||
/storage/*.gz
|
||||
.DS_Store
|
||||
/.vagrant
|
||||
*.pyc
|
||||
/__pycache__
|
||||
/venv
|
||||
__pycache__
|
||||
.vscode
|
||||
/dist
|
||||
/build
|
||||
/*.egg-info
|
||||
/*.egg
|
||||
testing-*.py
|
||||
/storage/full-reports/*.html
|
||||
/storage/logs/*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
language: python
|
||||
dist: bionic
|
||||
dist: focal
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/pip
|
||||
|
||||
28
Dockerfile
Normal file
28
Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
||||
FROM python:3.9-slim
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get -y install git build-essential libssl-dev \
|
||||
&& apt-get clean \
|
||||
&& pip install --upgrade pip
|
||||
|
||||
RUN pip3 install Cython numpy
|
||||
|
||||
# Prepare environment
|
||||
RUN mkdir /jesse-docker
|
||||
WORKDIR /jesse-docker
|
||||
|
||||
# Install TA-lib
|
||||
COPY docker_build_helpers/* /tmp/
|
||||
RUN cd /tmp && /tmp/install_ta-lib.sh && rm -r /tmp/*ta-lib*
|
||||
ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
|
||||
|
||||
# Install dependencies
|
||||
COPY requirements.txt /jesse-docker
|
||||
RUN pip3 install -r requirements.txt
|
||||
|
||||
# Build
|
||||
COPY . /jesse-docker
|
||||
RUN pip3 install -e .
|
||||
|
||||
WORKDIR /home
|
||||
28
DockerfileTest
Normal file
28
DockerfileTest
Normal file
@@ -0,0 +1,28 @@
|
||||
FROM python:3.9-slim
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get -y install build-essential libssl-dev \
|
||||
&& apt-get clean \
|
||||
&& pip install --upgrade pip
|
||||
|
||||
RUN pip3 install Cython numpy codecov pytest-cov
|
||||
|
||||
# Prepare environment
|
||||
RUN mkdir /jesse-docker
|
||||
WORKDIR /jesse-docker
|
||||
|
||||
# Install TA-lib
|
||||
COPY docker_build_helpers/* /tmp/
|
||||
RUN cd /tmp && /tmp/install_ta-lib.sh && rm -r /tmp/*ta-lib*
|
||||
ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
|
||||
|
||||
# Install dependencies
|
||||
COPY requirements.txt /jesse-docker
|
||||
RUN pip3 install -r requirements.txt
|
||||
|
||||
# Build
|
||||
COPY . /jesse-docker
|
||||
RUN pip3 install -e .
|
||||
|
||||
ENTRYPOINT pytest --cov=./ # && codecov
|
||||
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 |
17
docker_build_helpers/install_ta-lib.sh
Executable file
17
docker_build_helpers/install_ta-lib.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
if [ -z "$1" ]; then
|
||||
INSTALL_LOC=/usr/local
|
||||
else
|
||||
INSTALL_LOC=${1}
|
||||
fi
|
||||
echo "Installing to ${INSTALL_LOC}"
|
||||
if [ ! -f "${INSTALL_LOC}/lib/libta_lib.a" ]; then
|
||||
tar zxvf ta-lib-0.4.0-src.tar.gz
|
||||
cd ta-lib \
|
||||
&& sed -i.bak "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h \
|
||||
&& ./configure --prefix=${INSTALL_LOC}/ \
|
||||
&& make \
|
||||
&& which sudo && sudo make install || make install \
|
||||
&& echo "export LD_LIBRARY_PATH=/usr/local/lib" >> /root/.bashrc
|
||||
else
|
||||
echo "TA-lib already installed, skipping installation"
|
||||
fi
|
||||
BIN
docker_build_helpers/ta-lib-0.4.0-src.tar.gz
Normal file
BIN
docker_build_helpers/ta-lib-0.4.0-src.tar.gz
Normal file
Binary file not shown.
@@ -1,500 +1,495 @@
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pydoc import locate
|
||||
|
||||
import warnings
|
||||
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
|
||||
|
||||
# Hide the "FutureWarning: pandas.util.testing is deprecated." caused by empyrical
|
||||
import warnings
|
||||
|
||||
# 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():
|
||||
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():
|
||||
"""
|
||||
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():
|
||||
"""
|
||||
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():
|
||||
"""
|
||||
@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):
|
||||
"""
|
||||
|
||||
:param exc_type:
|
||||
:param exc_value:
|
||||
:param exc_traceback:
|
||||
:return:
|
||||
"""
|
||||
if issubclass(exc_type, KeyboardInterrupt):
|
||||
sys.excepthook(exc_type, exc_value, exc_traceback)
|
||||
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()
|
||||
|
||||
# handle Breaking exceptions
|
||||
if exc_type in [
|
||||
exceptions.ConfigException, 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
|
||||
from jesse.services import strategy_maker
|
||||
return strategy_maker.generate(json_request.name)
|
||||
|
||||
# send notifications if it's a live session
|
||||
if jh.is_live():
|
||||
jesse_logger.error(
|
||||
'{}: {}'.format(exc_type.__name__, exc_value)
|
||||
|
||||
@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):
|
||||
"""
|
||||
|
||||
:param args:
|
||||
:return:
|
||||
"""
|
||||
if args.exc_type == SystemExit:
|
||||
return
|
||||
|
||||
# handle Breaking exceptions
|
||||
if args.exc_type in [
|
||||
exceptions.ConfigException, 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
|
||||
@click.group()
|
||||
@click.version_option(pkg_resources.get_distribution("jesse").version)
|
||||
def cli():
|
||||
def cli() -> None:
|
||||
pass
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument('exchange', required=True, type=str)
|
||||
@click.argument('symbol', required=True, type=str)
|
||||
@click.argument('start_date', required=True, type=str)
|
||||
def import_candles(exchange, symbol, start_date):
|
||||
"""
|
||||
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)
|
||||
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, finish_date, debug, csv, json, fee, chart, tradingview):
|
||||
"""
|
||||
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.'
|
||||
)
|
||||
def optimize(start_date, finish_date, optimal_total, cpu, debug):
|
||||
"""
|
||||
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)
|
||||
return JSONResponse({'message': 'Started optimization...'}, status_code=202)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument('name', required=True, type=str)
|
||||
def make_strategy(name):
|
||||
@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):
|
||||
@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):
|
||||
"""
|
||||
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():
|
||||
"""
|
||||
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, debug, dev, fee):
|
||||
"""
|
||||
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, dev, fee):
|
||||
"""
|
||||
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")
|
||||
|
||||
255
jesse/config.py
255
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'
|
||||
},
|
||||
@@ -30,88 +25,177 @@ config = {
|
||||
'Sandbox': {
|
||||
'fee': 0,
|
||||
'type': 'spot',
|
||||
# used only in margin trading
|
||||
# used only in futures trading
|
||||
'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': 10000},
|
||||
{'asset': 'USDT', 'balance': 10_000},
|
||||
{'asset': 'BTC', 'balance': 0},
|
||||
],
|
||||
},
|
||||
|
||||
'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': {
|
||||
'type': 'margin',
|
||||
# used only in margin trading
|
||||
'settlement_currency': 'USD',
|
||||
'fee': 0.002,
|
||||
|
||||
# backtest mode only: accepted are 'spot' and 'futures'
|
||||
'type': 'futures',
|
||||
|
||||
# futures mode only
|
||||
'settlement_currency': 'USD',
|
||||
# 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': 10000},
|
||||
{'asset': 'USD', 'balance': 10000},
|
||||
{'asset': 'USDT', 'balance': 10_000},
|
||||
{'asset': 'USD', 'balance': 10_000},
|
||||
{'asset': 'BTC', 'balance': 0},
|
||||
],
|
||||
},
|
||||
|
||||
# https://www.binance.com
|
||||
'Binance': {
|
||||
'type': 'spot',
|
||||
# used only in margin trading
|
||||
'settlement_currency': 'USDT',
|
||||
'fee': 0.001,
|
||||
|
||||
# backtest mode only: accepted are 'spot' and 'futures'
|
||||
'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': 10000},
|
||||
{'asset': 'USDT', 'balance': 10_000},
|
||||
{'asset': 'BTC', 'balance': 0},
|
||||
],
|
||||
},
|
||||
|
||||
# https://www.binance.com
|
||||
'Binance Futures': {
|
||||
'type': 'margin',
|
||||
# used only in margin trading
|
||||
'fee': 0.0004,
|
||||
|
||||
# backtest mode only: accepted are 'spot' and 'futures'
|
||||
'type': 'futures',
|
||||
|
||||
# futures mode only
|
||||
'settlement_currency': 'USDT',
|
||||
'fee': 0.0002,
|
||||
# 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': 10000},
|
||||
{'asset': 'USDT', 'balance': 10_000},
|
||||
],
|
||||
},
|
||||
|
||||
# https://testnet.binancefuture.com
|
||||
'Testnet Binance Futures': {
|
||||
'type': 'margin',
|
||||
# used only in margin trading
|
||||
'fee': 0.0004,
|
||||
|
||||
# backtest mode only: accepted are 'spot' and 'futures'
|
||||
'type': 'futures',
|
||||
|
||||
# futures mode only
|
||||
'settlement_currency': 'USDT',
|
||||
'fee': 0.0002,
|
||||
# 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': 10000},
|
||||
{'asset': 'USDT', 'balance': 10_000},
|
||||
],
|
||||
},
|
||||
|
||||
# https://pro.coinbase.com
|
||||
'Coinbase': {
|
||||
'type': 'spot',
|
||||
# used only in margin trading
|
||||
'settlement_currency': 'USDT',
|
||||
'fee': 0.005,
|
||||
|
||||
# backtest mode only: accepted are 'spot' and 'futures'
|
||||
'type': 'futures',
|
||||
|
||||
# futures mode only
|
||||
'settlement_currency': 'USD',
|
||||
# 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': 10000},
|
||||
{'asset': 'USDT', 'balance': 10_000},
|
||||
{'asset': 'USD', 'balance': 10_000},
|
||||
{'asset': 'BTC', 'balance': 0},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
# 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
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
@@ -119,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',
|
||||
},
|
||||
|
||||
@@ -131,7 +215,7 @@ config = {
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
'data': {
|
||||
# The minimum number of warmup candles that is loaded before each session.
|
||||
'warmup_candles_num': 210,
|
||||
'warmup_candles_num': 240,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -165,28 +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):
|
||||
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 margin trading
|
||||
'settlement_currency': 'USDT',
|
||||
'fee': 0,
|
||||
'assets': [
|
||||
{'asset': 'USDT', 'balance': 10000},
|
||||
{'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():
|
||||
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_FILLED = 'PARTIALLY FILLED'
|
||||
QUEUED = 'QUEUED'
|
||||
LIQUIDATED = 'LIQUIDATED'
|
||||
|
||||
|
||||
class timeframes:
|
||||
@@ -21,12 +23,14 @@ class timeframes:
|
||||
MINUTE_5 = '5m'
|
||||
MINUTE_15 = '15m'
|
||||
MINUTE_30 = '30m'
|
||||
MINUTE_45 = '45m'
|
||||
HOUR_1 = '1h'
|
||||
HOUR_2 = '2h'
|
||||
HOUR_3 = '3h'
|
||||
HOUR_4 = '4h'
|
||||
HOUR_6 = '6h'
|
||||
HOUR_8 = '8h'
|
||||
HOUR_12 = '12h'
|
||||
DAY_1 = '1D'
|
||||
|
||||
|
||||
@@ -38,21 +42,6 @@ class colors:
|
||||
BLACK = 'black'
|
||||
|
||||
|
||||
class order_roles:
|
||||
OPEN_POSITION = 'OPEN POSITION'
|
||||
CLOSE_POSITION = 'CLOSE POSITION'
|
||||
INCREASE_POSITION = 'INCREASE POSITION'
|
||||
REDUCE_POSITION = 'REDUCE POSITION'
|
||||
|
||||
|
||||
class order_flags:
|
||||
OCO = 'OCO'
|
||||
POST_ONLY = 'PostOnly'
|
||||
CLOSE = 'Close'
|
||||
HIDDEN = 'Hidden'
|
||||
REDUCE_ONLY = 'ReduceOnly'
|
||||
|
||||
|
||||
class order_types:
|
||||
MARKET = 'MARKET'
|
||||
LIMIT = 'LIMIT'
|
||||
|
||||
@@ -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,16 +46,29 @@ class ExchangeNotResponding(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ExchangeRejectedOrder(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidShape(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ConfigException(Exception):
|
||||
class InvalidConfig(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidTimeframe(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NegativeBalance(Exception):
|
||||
pass
|
||||
|
||||
|
||||
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,65 +8,25 @@ class Exchange(ABC):
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def market_order(self, symbol, qty, current_price, side, role, flags):
|
||||
"""
|
||||
|
||||
:param symbol:
|
||||
:param qty:
|
||||
:param current_price:
|
||||
:param side:
|
||||
:param role:
|
||||
:param flags:
|
||||
"""
|
||||
def market_order(self, symbol: str, qty: float, current_price: float, side: str, reduce_only: bool) -> Order:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def limit_order(self, symbol, qty, price, side, role, flags):
|
||||
"""
|
||||
|
||||
:param symbol:
|
||||
:param qty:
|
||||
:param price:
|
||||
:param side:
|
||||
:param role:
|
||||
:param flags:
|
||||
"""
|
||||
def limit_order(self, symbol: str, qty: float, price: float, side: str, reduce_only: bool) -> Order:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def stop_order(self, symbol, qty, price, side, role, flags):
|
||||
"""
|
||||
|
||||
:param symbol:
|
||||
:param qty:
|
||||
:param price:
|
||||
:param side:
|
||||
:param role:
|
||||
:param flags:
|
||||
"""
|
||||
def stop_order(self, symbol: str, qty: float, price: float, side: str, reduce_only: bool) -> Order:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def cancel_all_orders(self, symbol):
|
||||
"""
|
||||
|
||||
:param symbol:
|
||||
"""
|
||||
def cancel_all_orders(self, symbol: str) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def cancel_order(self, symbol, order_id):
|
||||
"""
|
||||
|
||||
:param symbol:
|
||||
:param order_id:
|
||||
"""
|
||||
def cancel_order(self, symbol: str, order_id: str) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_exec_inst(self, flags):
|
||||
"""
|
||||
|
||||
:param flags:
|
||||
"""
|
||||
def _fetch_precisions(self) -> None:
|
||||
pass
|
||||
|
||||
@@ -3,37 +3,24 @@ 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, reduce_only: bool) -> Order:
|
||||
order = Order({
|
||||
'id': jh.generate_unique_id(),
|
||||
'symbol': symbol,
|
||||
'exchange': self.name,
|
||||
'side': side,
|
||||
'type': order_types.MARKET,
|
||||
'flag': self.get_exec_inst(flags),
|
||||
'reduce_only': reduce_only,
|
||||
'qty': jh.prepare_qty(qty, side),
|
||||
'price': current_price,
|
||||
'role': role
|
||||
})
|
||||
|
||||
store.orders.add_order(order)
|
||||
@@ -42,65 +29,39 @@ 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, reduce_only: bool) -> Order:
|
||||
order = Order({
|
||||
'id': jh.generate_unique_id(),
|
||||
'symbol': symbol,
|
||||
'exchange': self.name,
|
||||
'side': side,
|
||||
'type': order_types.LIMIT,
|
||||
'flag': self.get_exec_inst(flags),
|
||||
'reduce_only': reduce_only,
|
||||
'qty': jh.prepare_qty(qty, side),
|
||||
'price': price,
|
||||
'role': role
|
||||
})
|
||||
|
||||
store.orders.add_order(order)
|
||||
|
||||
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, reduce_only: bool) -> Order:
|
||||
order = Order({
|
||||
'id': jh.generate_unique_id(),
|
||||
'symbol': symbol,
|
||||
'exchange': self.name,
|
||||
'side': side,
|
||||
'type': order_types.STOP,
|
||||
'flag': self.get_exec_inst(flags),
|
||||
'reduce_only': reduce_only,
|
||||
'qty': jh.prepare_qty(qty, side),
|
||||
'price': price,
|
||||
'role': role
|
||||
})
|
||||
|
||||
store.orders.add_order(order)
|
||||
|
||||
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 +69,10 @@ 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:
|
||||
"""
|
||||
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:
|
||||
|
||||
553
jesse/helpers.py
553
jesse/helpers.py
@@ -5,7 +5,8 @@ import random
|
||||
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
|
||||
@@ -13,21 +14,21 @@ import numpy as np
|
||||
CACHED_CONFIG = dict()
|
||||
|
||||
|
||||
def app_currency():
|
||||
def app_currency() -> str:
|
||||
from jesse.routes import router
|
||||
return quote_asset(router.routes[0].symbol)
|
||||
|
||||
|
||||
def app_mode():
|
||||
def app_mode() -> str:
|
||||
from jesse.config import config
|
||||
return config['app']['trading_mode']
|
||||
|
||||
|
||||
def arrow_to_timestamp(arrow_time):
|
||||
def arrow_to_timestamp(arrow_time: arrow.arrow.Arrow) -> int:
|
||||
return arrow_time.int_timestamp * 1000
|
||||
|
||||
|
||||
def base_asset(symbol: str):
|
||||
def base_asset(symbol: str) -> str:
|
||||
return symbol.split('-')[0]
|
||||
|
||||
|
||||
@@ -49,11 +50,16 @@ def binary_search(arr: list, item) -> int:
|
||||
return -1
|
||||
|
||||
|
||||
def clean_orderbook_list(arr):
|
||||
def class_iter(Class):
|
||||
return (value for variable, value in vars(Class).items() if
|
||||
not callable(getattr(Class, variable)) and not variable.startswith("__"))
|
||||
|
||||
|
||||
def clean_orderbook_list(arr) -> List[List[float]]:
|
||||
return [[float(i[0]), float(i[1])] for i in arr]
|
||||
|
||||
|
||||
def color(msg_text: str, msg_color: str):
|
||||
def color(msg_text: str, msg_color: str) -> str:
|
||||
if not msg_text:
|
||||
return ''
|
||||
|
||||
@@ -71,36 +77,52 @@ def color(msg_text: str, msg_color: 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')
|
||||
|
||||
|
||||
def convert_number(old_max, old_min, new_max, new_min, old_value):
|
||||
def convert_number(old_max: float, old_min: float, new_max: float, new_min: float, old_value: float) -> float:
|
||||
"""
|
||||
convert a number from one range (ex 40-119) to another
|
||||
range (ex 0-30) while keeping the ratio.
|
||||
"""
|
||||
# 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 (((old_value - old_min) * new_range) / old_range) + new_min
|
||||
|
||||
return new_value
|
||||
|
||||
def dashless_symbol(symbol):
|
||||
def dashless_symbol(symbol: str) -> str:
|
||||
return symbol.replace("-", "")
|
||||
|
||||
def date_diff_in_days(date1, date2):
|
||||
|
||||
def dashy_symbol(symbol: str) -> str:
|
||||
# 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:
|
||||
if type(date1) is not arrow.arrow.Arrow or type(
|
||||
date2) is not arrow.arrow.Arrow:
|
||||
raise TypeError('dates must be Arrow instances')
|
||||
|
||||
dif = date2 - date1
|
||||
|
||||
return abs(dif.days)
|
||||
|
||||
|
||||
@@ -130,11 +152,10 @@ def dna_to_hp(strategy_hp, dna: str):
|
||||
raise TypeError('Only int and float types are implemented')
|
||||
|
||||
hp[h['name']] = decoded_gene
|
||||
|
||||
return hp
|
||||
|
||||
|
||||
def dump_exception():
|
||||
def dump_exception() -> None:
|
||||
"""
|
||||
a useful debugging helper
|
||||
"""
|
||||
@@ -143,7 +164,8 @@ def dump_exception():
|
||||
terminate_app()
|
||||
|
||||
|
||||
def estimate_average_price(order_qty, order_price, current_qty, current_entry_price):
|
||||
def estimate_average_price(order_qty: float, order_price: float, current_qty: float,
|
||||
current_entry_price: float) -> float:
|
||||
"""Estimates the new entry price for the position.
|
||||
This is used after having a new order and updating the currently holding position.
|
||||
|
||||
@@ -160,7 +182,7 @@ def estimate_average_price(order_qty, order_price, current_qty, current_entry_pr
|
||||
current_entry_price) / (abs(order_qty) + abs(current_qty))
|
||||
|
||||
|
||||
def estimate_PNL(qty, entry_price, exit_price, trade_type, trading_fee=0):
|
||||
def estimate_PNL(qty: float, entry_price: float, exit_price: float, trade_type: str, trading_fee: float = 0) -> float:
|
||||
qty = abs(qty)
|
||||
profit = qty * (exit_price - entry_price)
|
||||
|
||||
@@ -172,7 +194,7 @@ def estimate_PNL(qty, entry_price, exit_price, trade_type, trading_fee=0):
|
||||
return profit - fee
|
||||
|
||||
|
||||
def estimate_PNL_percentage(qty, entry_price, exit_price, trade_type):
|
||||
def estimate_PNL_percentage(qty: float, entry_price: float, exit_price: float, trade_type: str) -> float:
|
||||
qty = abs(qty)
|
||||
profit = qty * (exit_price - entry_price)
|
||||
|
||||
@@ -186,20 +208,34 @@ def file_exists(path: str) -> bool:
|
||||
return os.path.isfile(path)
|
||||
|
||||
|
||||
def floor_with_precision(num, precision=0):
|
||||
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
|
||||
|
||||
|
||||
def format_currency(num):
|
||||
def format_currency(num: float) -> str:
|
||||
return f'{num:,}'
|
||||
|
||||
|
||||
def generate_unique_id():
|
||||
def generate_unique_id() -> str:
|
||||
return str(uuid.uuid4())
|
||||
|
||||
|
||||
def get_candle_source(candles: np.ndarray, source_type="close") -> np.ndarray:
|
||||
def get_arrow(timestamp: int) -> arrow.arrow.Arrow:
|
||||
return timestamp_to_arrow(timestamp)
|
||||
|
||||
|
||||
def get_candle_source(candles: np.ndarray, source_type: str = "close") -> np.ndarray:
|
||||
"""
|
||||
Returns the candles corresponding the selected type.
|
||||
|
||||
@@ -228,7 +264,7 @@ def get_candle_source(candles: np.ndarray, source_type="close") -> np.ndarray:
|
||||
raise ValueError('type string not recognised')
|
||||
|
||||
|
||||
def get_config(keys: str, default=None):
|
||||
def get_config(keys: str, default: Any = None) -> Any:
|
||||
"""
|
||||
Gets keys as a single string separated with "." and returns value.
|
||||
Also accepts a default value so that the app would work even if
|
||||
@@ -242,29 +278,39 @@ def get_config(keys: str, default=None):
|
||||
if not str:
|
||||
raise ValueError('keys string cannot be empty')
|
||||
|
||||
if not keys in CACHED_CONFIG:
|
||||
from functools import reduce
|
||||
from jesse.config import config
|
||||
CACHED_CONFIG[keys] = reduce(lambda d, k: d.get(k, default) if isinstance(d, dict) else default,
|
||||
keys.split("."), config)
|
||||
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
|
||||
CACHED_CONFIG[keys] = reduce(lambda d, k: d.get(k, default) if isinstance(d, dict) else default,
|
||||
keys.split("."), config)
|
||||
|
||||
return CACHED_CONFIG[keys]
|
||||
|
||||
|
||||
def get_strategy_class(strategy_name):
|
||||
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:
|
||||
return hashlib.md5(msg.encode()).hexdigest()
|
||||
|
||||
|
||||
def insert_list(index: int, item, arr: list):
|
||||
def insert_list(index: int, item, arr: list) -> list:
|
||||
"""
|
||||
helper to insert an item in a Python List without removing the item
|
||||
"""
|
||||
@@ -274,71 +320,83 @@ def insert_list(index: int, item, arr: list):
|
||||
return arr[:index] + [item] + arr[index:]
|
||||
|
||||
|
||||
def is_backtesting():
|
||||
def is_backtesting() -> bool:
|
||||
from jesse.config import config
|
||||
return config['app']['trading_mode'] == 'backtest'
|
||||
|
||||
|
||||
def is_collecting_data():
|
||||
def is_collecting_data() -> bool:
|
||||
from jesse.config import config
|
||||
return config['app']['trading_mode'] == 'collect'
|
||||
|
||||
|
||||
def is_debuggable(debug_item):
|
||||
def is_debuggable(debug_item) -> bool:
|
||||
from jesse.config import config
|
||||
return is_debugging() and config['env']['logging'][debug_item]
|
||||
|
||||
|
||||
def is_debugging():
|
||||
def is_debugging() -> bool:
|
||||
from jesse.config import config
|
||||
return config['app']['debug_mode']
|
||||
|
||||
|
||||
def is_importing_candles():
|
||||
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():
|
||||
def is_live() -> bool:
|
||||
return is_livetrading() or is_paper_trading()
|
||||
|
||||
|
||||
def is_livetrading():
|
||||
def is_livetrading() -> bool:
|
||||
from jesse.config import config
|
||||
return config['app']['trading_mode'] == 'livetrade'
|
||||
|
||||
|
||||
def is_optimizing():
|
||||
def is_optimizing() -> bool:
|
||||
from jesse.config import config
|
||||
return config['app']['trading_mode'] == 'optimize'
|
||||
|
||||
|
||||
def is_paper_trading():
|
||||
def is_paper_trading() -> bool:
|
||||
from jesse.config import config
|
||||
return config['app']['trading_mode'] == 'papertrade'
|
||||
|
||||
|
||||
def is_test_driving():
|
||||
def is_test_driving() -> bool:
|
||||
from jesse.config import config
|
||||
return config['app']['is_test_driving']
|
||||
|
||||
|
||||
def is_unit_testing():
|
||||
return "pytest" in sys.modules
|
||||
def is_unit_testing() -> bool:
|
||||
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 key(exchange, symbol, timeframe=None):
|
||||
def is_valid_uuid(uuid_to_test:str, version: int = 4) -> bool:
|
||||
try:
|
||||
uuid_obj = uuid.UUID(uuid_to_test, version=version)
|
||||
except ValueError:
|
||||
return False
|
||||
return str(uuid_obj) == uuid_to_test
|
||||
|
||||
|
||||
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):
|
||||
def max_timeframe(timeframes_list: list) -> str:
|
||||
from jesse.enums import timeframes
|
||||
|
||||
if timeframes.DAY_1 in timeframes_list:
|
||||
return timeframes.DAY_1
|
||||
if timeframes.HOUR_12 in timeframes_list:
|
||||
return timeframes.HOUR_12
|
||||
if timeframes.HOUR_8 in timeframes_list:
|
||||
return timeframes.HOUR_8
|
||||
if timeframes.HOUR_6 in timeframes_list:
|
||||
@@ -351,6 +409,8 @@ def max_timeframe(timeframes_list):
|
||||
return timeframes.HOUR_2
|
||||
if timeframes.HOUR_1 in timeframes_list:
|
||||
return timeframes.HOUR_1
|
||||
if timeframes.MINUTE_45 in timeframes_list:
|
||||
return timeframes.MINUTE_45
|
||||
if timeframes.MINUTE_30 in timeframes_list:
|
||||
return timeframes.MINUTE_30
|
||||
if timeframes.MINUTE_15 in timeframes_list:
|
||||
@@ -363,27 +423,51 @@ def max_timeframe(timeframes_list):
|
||||
return timeframes.MINUTE_1
|
||||
|
||||
|
||||
def normalize(x, x_min, x_max):
|
||||
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_to_timestamp():
|
||||
if not (is_live() or is_collecting_data() or is_importing_candles()):
|
||||
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(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 now():
|
||||
return now_to_timestamp()
|
||||
def current_1m_candle_timestamp():
|
||||
return arrow.utcnow().floor('minute').int_timestamp * 1000
|
||||
|
||||
|
||||
def np_shift(arr: np.ndarray, num: int, fill_value=0):
|
||||
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[axis] = idx
|
||||
return arr[tuple(slc)]
|
||||
|
||||
|
||||
def np_shift(arr: np.ndarray, num: int, fill_value=0) -> np.ndarray:
|
||||
result = np.empty_like(arr)
|
||||
|
||||
if num > 0:
|
||||
@@ -398,17 +482,18 @@ def np_shift(arr: np.ndarray, num: int, fill_value=0):
|
||||
return result
|
||||
|
||||
|
||||
def opposite_side(s):
|
||||
def opposite_side(s: str) -> str:
|
||||
from jesse.enums import sides
|
||||
|
||||
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):
|
||||
def opposite_type(t: str) -> str:
|
||||
from jesse.enums import trade_types
|
||||
|
||||
if t == trade_types.LONG:
|
||||
@@ -418,15 +503,15 @@ def opposite_type(t):
|
||||
raise ValueError('unsupported type')
|
||||
|
||||
|
||||
def orderbook_insertion_index_search(arr, target, ascending=True):
|
||||
def orderbook_insertion_index_search(arr, target: int, ascending: bool = True) -> Tuple[bool, int]:
|
||||
target = target[0]
|
||||
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:
|
||||
@@ -437,30 +522,19 @@ def orderbook_insertion_index_search(arr, target, ascending=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):
|
||||
"""
|
||||
|
||||
:param p:
|
||||
:param ascending:
|
||||
:param unit:
|
||||
:return:
|
||||
"""
|
||||
def orderbook_trim_price(p: float, ascending: bool, unit: float) -> float:
|
||||
if ascending:
|
||||
trimmed = np.ceil(p / unit) * unit
|
||||
if math.log10(unit) < 0:
|
||||
@@ -473,33 +547,32 @@ def orderbook_trim_price(p: float, ascending: bool, unit: float):
|
||||
return p if trimmed == p - unit else trimmed
|
||||
|
||||
|
||||
def prepare_qty(qty, side):
|
||||
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):
|
||||
def quote_asset(symbol: str) -> str:
|
||||
try:
|
||||
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=8):
|
||||
return ''.join(random.choice(string.ascii_letters) for i in range(num_characters))
|
||||
def random_str(num_characters: int = 8) -> str:
|
||||
return ''.join(random.choice(string.ascii_letters) for _ in range(num_characters))
|
||||
|
||||
|
||||
def readable_duration(seconds, granularity=2):
|
||||
def readable_duration(seconds: int, granularity: int = 2) -> str:
|
||||
intervals = (
|
||||
('weeks', 604800), # 60 * 60 * 24 * 7
|
||||
('days', 86400), # 60 * 60 * 24
|
||||
@@ -509,6 +582,7 @@ def readable_duration(seconds, granularity=2):
|
||||
)
|
||||
|
||||
result = []
|
||||
seconds = int(seconds)
|
||||
|
||||
for name, count in intervals:
|
||||
value = seconds // count
|
||||
@@ -516,7 +590,7 @@ def readable_duration(seconds, granularity=2):
|
||||
seconds -= value * count
|
||||
if value == 1:
|
||||
name = name.rstrip('s')
|
||||
result.append("{} {}".format(value, name))
|
||||
result.append(f"{value} {name}")
|
||||
return ', '.join(result[:granularity])
|
||||
|
||||
|
||||
@@ -524,44 +598,52 @@ def relative_to_absolute(path: str) -> str:
|
||||
return os.path.abspath(path)
|
||||
|
||||
|
||||
def round_price_for_live_mode(price, roundable_price):
|
||||
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 | nd.array
|
||||
: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, roundable_qty):
|
||||
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))
|
||||
# for qty rounding down is important to prevent InsufficenMargin
|
||||
rounded = round_decimals_down(roundable_qty, precision)
|
||||
|
||||
if price < 1:
|
||||
qty_round_precision = 0
|
||||
else:
|
||||
qty_round_precision = n + 1
|
||||
if qty_round_precision > 3:
|
||||
qty_round_precision = 3
|
||||
for index, q in enumerate(rounded):
|
||||
if q == 0.0:
|
||||
rounded[index] = 1 / 10 ** precision
|
||||
|
||||
return np.round(roundable_qty, qty_round_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:
|
||||
@@ -572,9 +654,12 @@ def should_execute_silently() -> bool:
|
||||
return is_optimizing() or is_unit_testing()
|
||||
|
||||
|
||||
def side_to_type(s):
|
||||
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:
|
||||
@@ -582,14 +667,21 @@ def side_to_type(s):
|
||||
raise ValueError
|
||||
|
||||
|
||||
def string_after_character(string: str, character: 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 style(msg_text: str, msg_style: str):
|
||||
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
|
||||
|
||||
@@ -602,15 +694,32 @@ def style(msg_text: str, msg_style: str):
|
||||
raise ValueError('unsupported style')
|
||||
|
||||
|
||||
def terminate_app():
|
||||
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 timeframe_to_one_minutes(timeframe):
|
||||
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
|
||||
|
||||
@@ -620,36 +729,34 @@ def timeframe_to_one_minutes(timeframe):
|
||||
timeframes.MINUTE_5: 5,
|
||||
timeframes.MINUTE_15: 15,
|
||||
timeframes.MINUTE_30: 30,
|
||||
timeframes.MINUTE_45: 45,
|
||||
timeframes.HOUR_1: 60,
|
||||
timeframes.HOUR_2: 60 * 2,
|
||||
timeframes.HOUR_3: 60 * 3,
|
||||
timeframes.HOUR_4: 60 * 4,
|
||||
timeframes.HOUR_6: 60 * 6,
|
||||
timeframes.HOUR_8: 60 * 8,
|
||||
timeframes.HOUR_12: 60 * 12,
|
||||
timeframes.DAY_1: 60 * 24,
|
||||
}
|
||||
|
||||
try:
|
||||
return dic[timeframe]
|
||||
except KeyError:
|
||||
all_timeframes = [timeframe for timeframe in class_iter(timeframes)]
|
||||
raise InvalidTimeframe(
|
||||
'Timeframe "{}" is invalid. Supported timeframes are 1m, 3m, 5m, 15m, 30m, 1h, 2h, 3h, 4h, 6h, 8h, 1D'.format(
|
||||
timeframe))
|
||||
f'Timeframe "{timeframe}" is invalid. Supported timeframes are {", ".join(all_timeframes)}.')
|
||||
|
||||
|
||||
def timestamp_to_arrow(timestamp):
|
||||
def timestamp_to_arrow(timestamp: int) -> arrow.arrow.Arrow:
|
||||
return arrow.get(timestamp / 1000)
|
||||
|
||||
|
||||
def get_arrow(timestamp):
|
||||
return timestamp_to_arrow(timestamp)
|
||||
|
||||
|
||||
def timestamp_to_date(timestamp: int) -> str:
|
||||
return str(arrow.get(timestamp / 1000))[:10]
|
||||
|
||||
|
||||
def timestamp_to_time(timestamp):
|
||||
def timestamp_to_time(timestamp: int) -> str:
|
||||
return str(arrow.get(timestamp / 1000))
|
||||
|
||||
|
||||
@@ -662,7 +769,7 @@ def today_to_timestamp() -> int:
|
||||
return arrow.utcnow().floor('day').int_timestamp * 1000
|
||||
|
||||
|
||||
def type_to_side(t):
|
||||
def type_to_side(t: str) -> str:
|
||||
from jesse.enums import trade_types, sides
|
||||
|
||||
if t == trade_types.LONG:
|
||||
@@ -681,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,68 +4,95 @@ 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
|
||||
from .bop import bop
|
||||
from .cc import cc
|
||||
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 .correlation_cycle import correlation_cycle
|
||||
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
|
||||
from .dema import dema
|
||||
from .devstop import devstop
|
||||
from .di import di
|
||||
from .dm import dm
|
||||
from .dx import dx
|
||||
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
|
||||
from .fwma import fwma
|
||||
from .gatorosc import gatorosc
|
||||
from .gauss import gauss
|
||||
from .hma import hma
|
||||
from .high_pass import high_pass
|
||||
from .high_pass_2_pole import high_pass_2_pole
|
||||
from .hma import hma
|
||||
from .ht_dcperiod import ht_dcperiod
|
||||
from .ht_dcphase import ht_dcphase
|
||||
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 .marketfi import marketfi
|
||||
from .mean_ad import mean_ad
|
||||
from .median_ad import median_ad
|
||||
from .medprice import medprice
|
||||
from .mfi import mfi
|
||||
from .midpoint import midpoint
|
||||
@@ -73,31 +100,48 @@ 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 .roofing import roofing
|
||||
from .rocp import rocp
|
||||
from .rocr import rocr
|
||||
from .rocr100 import rocr100
|
||||
from .roofing import roofing
|
||||
from .rsi import rsi
|
||||
from .rsmk import rsmk
|
||||
from .rsx import rsx
|
||||
from .rvi import rvi
|
||||
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
|
||||
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
|
||||
@@ -106,21 +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 .vpci import vpci
|
||||
from .vpt import vpt
|
||||
from .vwma import vwma
|
||||
from .vwmacd import vwmacd
|
||||
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 .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,20 +3,21 @@ from collections import namedtuple
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
AC = namedtuple('AC', ['osc', 'change'])
|
||||
|
||||
|
||||
def acosc(candles: np.ndarray, sequential=False) -> AC:
|
||||
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)
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
med = talib.MEDPRICE(candles[:, 3], candles[:, 4])
|
||||
ao = talib.SMA(med, 5) - talib.SMA(med, 34)
|
||||
|
||||
@@ -3,22 +3,20 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
def ad(candles: np.ndarray, sequential=False) -> Union[float, np.ndarray]:
|
||||
|
||||
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
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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,25 +3,24 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
def adosc(candles: np.ndarray, fastperiod=3, slowperiod=10, sequential=False) -> Union[float, np.ndarray]:
|
||||
|
||||
def adosc(candles: np.ndarray, fast_period: int = 3, slow_period: int = 10, sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
ADOSC - Chaikin A/D Oscillator
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param fastperiod: int - default: 3
|
||||
:param slowperiod: int - default: 10
|
||||
:param sequential: bool - default=False
|
||||
:param fast_period: int - default: 3
|
||||
:param slow_period: int - default: 10
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
res = talib.ADOSC(candles[:, 3], candles[:, 4], candles[:, 2], candles[:, 5], fastperiod=fastperiod,
|
||||
slowperiod=slowperiod)
|
||||
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,23 +3,21 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
def adx(candles: np.ndarray, period=14, sequential=False) -> Union[float, np.ndarray]:
|
||||
|
||||
def adx(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
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
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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,23 +3,21 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
def adxr(candles: np.ndarray, period=14, sequential=False) -> Union[float, np.ndarray]:
|
||||
|
||||
def adxr(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
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
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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,23 +2,22 @@ from collections import namedtuple
|
||||
|
||||
import numpy as np
|
||||
|
||||
from jesse.helpers import get_candle_source, np_shift
|
||||
from jesse.helpers import get_candle_source, np_shift, slice_candles
|
||||
|
||||
AG = namedtuple('AG', ['jaw', 'teeth', 'lips'])
|
||||
|
||||
|
||||
def alligator(candles: np.ndarray, source_type="close", sequential=False) -> AG:
|
||||
def alligator(candles: np.ndarray, source_type: str = "close", sequential: bool = False) -> AG:
|
||||
"""
|
||||
Alligator
|
||||
|
||||
: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)
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
@@ -32,7 +31,7 @@ def alligator(candles: np.ndarray, source_type="close", sequential=False) -> AG:
|
||||
return AG(jaw[-1], teeth[-1], lips[-1])
|
||||
|
||||
|
||||
def numpy_ewma(data, window):
|
||||
def numpy_ewma(data: np.ndarray, window: int):
|
||||
"""
|
||||
|
||||
:param data:
|
||||
@@ -40,13 +39,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()
|
||||
|
||||
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,20 +3,21 @@ from collections import namedtuple
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
AO = namedtuple('AO', ['osc', 'change'])
|
||||
|
||||
|
||||
def ao(candles: np.ndarray, sequential=False) -> AO:
|
||||
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)
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
med = talib.MEDPRICE(candles[:, 3], candles[:, 4])
|
||||
res = talib.SMA(med, 5) - talib.SMA(med, 34)
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
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_candle_source, slice_candles
|
||||
|
||||
|
||||
def apo(candles: np.ndarray, fastperiod=12, slowperiod=26, matype=0, source_type="close", sequential=False) -> Union[
|
||||
float, np.ndarray]:
|
||||
def apo(candles: np.ndarray, fast_period: int = 12, slow_period: int = 26, matype: int = 0, source_type: str = "close",
|
||||
sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
APO - Absolute Price Oscillator
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param fastperiod: int - default: 12
|
||||
:param slowperiod: int - default: 26
|
||||
:param fast_period: int - default: 12
|
||||
: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
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
res = talib.APO(source, fastperiod=fastperiod, slowperiod=slowperiod, 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,21 +3,22 @@ from collections import namedtuple
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
AROON = namedtuple('AROON', ['down', 'up'])
|
||||
|
||||
|
||||
def aroon(candles: np.ndarray, period=14, sequential=False) -> AROON:
|
||||
def aroon(candles: np.ndarray, period: int = 14, sequential: bool = False) -> AROON:
|
||||
"""
|
||||
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)
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
aroondown, aroonup = talib.AROON(candles[:, 3], candles[:, 4], timeperiod=period)
|
||||
|
||||
|
||||
@@ -3,23 +3,21 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
def aroonosc(candles: np.ndarray, period=14, sequential=False) -> Union[float, np.ndarray]:
|
||||
|
||||
def aroonosc(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
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
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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,23 +3,21 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
def atr(candles: np.ndarray, period=14, sequential=False) -> Union[float, np.ndarray]:
|
||||
|
||||
def atr(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
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
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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,22 +3,20 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
def avgprice(candles: np.ndarray, sequential=False) -> Union[float, np.ndarray]:
|
||||
|
||||
def avgprice(candles: np.ndarray, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
AVGPRICE - Average Price
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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,23 +3,21 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
def beta(candles: np.ndarray, period=5, sequential=False) -> Union[float, np.ndarray]:
|
||||
|
||||
def beta(candles: np.ndarray, period: int = 5, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
BETA - Beta
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 5
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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,18 @@ 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_candle_source, slice_candles
|
||||
|
||||
BollingerBands = namedtuple('BollingerBands', ['upperband', 'middleband', 'lowerband'])
|
||||
|
||||
|
||||
def bollinger_bands(candles: np.ndarray, period=20, devup=2, devdn=2, matype=0, source_type="close",
|
||||
sequential=False) -> BollingerBands:
|
||||
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:
|
||||
"""
|
||||
BBANDS - Bollinger Bands
|
||||
|
||||
@@ -18,17 +22,27 @@ def bollinger_bands(candles: np.ndarray, period=20, devup=2, devdn=2, matype=0,
|
||||
: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)
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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,12 +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_candle_source, slice_candles
|
||||
|
||||
|
||||
def bollinger_bands_width(candles: np.ndarray, period=20, devup=2, devdn=2, matype=0, source_type="close",
|
||||
sequential=False) -> Union[float, np.ndarray]:
|
||||
def bollinger_bands_width(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) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
BBW - Bollinger Bands Width - Bollinger Bands Bandwidth
|
||||
|
||||
@@ -16,19 +21,31 @@ def bollinger_bands_width(candles: np.ndarray, period=20, devup=2, devdn=2, maty
|
||||
: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)
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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,22 +3,20 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
def bop(candles: np.ndarray, sequential=False) -> Union[float, np.ndarray]:
|
||||
|
||||
def bop(candles: np.ndarray, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
BOP - Balance Of Power
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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,10 +3,12 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
|
||||
def cc(candles: np.ndarray, wma_period=10, roc_short_period=11, roc_long_period=14, source_type="close", sequential=False) -> Union[float, np.ndarray]:
|
||||
def cc(candles: np.ndarray, wma_period: int = 10, roc_short_period: int = 11, roc_long_period: int = 14,
|
||||
source_type: str = "close",
|
||||
sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
CC - Coppock Curve
|
||||
|
||||
@@ -15,14 +17,14 @@ def cc(candles: np.ndarray, wma_period=10, roc_short_period=11, roc_long_period=
|
||||
: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
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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), timeperiod=wma_period)
|
||||
res = talib.WMA(talib.ROC(source, timeperiod=roc_long_period) + talib.ROC(source, timeperiod=roc_short_period),
|
||||
timeperiod=wma_period)
|
||||
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -3,23 +3,21 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
def cci(candles: np.ndarray, period=14, sequential=False) -> Union[float, np.ndarray]:
|
||||
|
||||
def cci(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
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
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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]
|
||||
|
||||
34
jesse/indicators/cfo.py
Normal file
34
jesse/indicators/cfo.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def cfo(candles: np.ndarray, period: int = 14, scalar: float = 100, source_type: str = "close",
|
||||
sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
CFO - Chande Forcast Oscillator
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 14
|
||||
:param scalar: float - default: 100
|
||||
: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)
|
||||
|
||||
res = scalar * (source - talib.LINEARREG(source, timeperiod=period))
|
||||
res /= source
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
46
jesse/indicators/cg.py
Normal file
46
jesse/indicators/cg.py
Normal file
@@ -0,0 +1,46 @@
|
||||
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, same_length, slice_candles
|
||||
|
||||
|
||||
def cg(candles: np.ndarray, period: int = 10, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
Center of Gravity (CG)
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 10
|
||||
: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)
|
||||
res = go_fast(source, period)
|
||||
|
||||
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(source.size):
|
||||
if i > period:
|
||||
num = 0
|
||||
denom = 0
|
||||
for count in range(period - 1):
|
||||
close = source[i - count]
|
||||
if not np.isnan(close):
|
||||
num += (1 + count) * close
|
||||
denom += close
|
||||
result = -num / denom if denom != 0 else 0
|
||||
res[i] = result
|
||||
return res
|
||||
@@ -4,21 +4,23 @@ import numpy as np
|
||||
import talib
|
||||
from scipy.ndimage.filters import maximum_filter1d, minimum_filter1d
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
def chande(candles: np.ndarray, period=22, mult=3.0, direction="long", sequential=False) -> Union[float, np.ndarray]:
|
||||
|
||||
def chande(candles: np.ndarray, period: int = 22, mult: float = 3.0, direction: str = "long",
|
||||
sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
Chandelier Exits
|
||||
|
||||
: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
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
candles_close = candles[:, 2]
|
||||
candles_high = candles[:, 3]
|
||||
@@ -38,10 +40,10 @@ def chande(candles: np.ndarray, period=22, mult=3.0, direction="long", sequentia
|
||||
return result if sequential else result[-1]
|
||||
|
||||
|
||||
def filter1d_same(a, W, type, 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,26 +4,24 @@ import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def cmo(candles: np.ndarray, period=14, source_type="close", sequential=False) -> Union[float, np.ndarray]:
|
||||
def cmo(candles: np.ndarray, period: int = 14, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
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
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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,23 +3,21 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
def correl(candles: np.ndarray, period=5, sequential=False) -> Union[float, np.ndarray]:
|
||||
|
||||
def correl(candles: np.ndarray, period: int = 5, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
CORREL - Pearson's Correlation Coefficient (r)
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 5
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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,14 +1,18 @@
|
||||
import math
|
||||
from collections import namedtuple
|
||||
|
||||
import numpy as np
|
||||
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_candle_source, np_shift, slice_candles
|
||||
|
||||
CC = namedtuple('CC', ['real', 'imag', 'angle', 'state'])
|
||||
|
||||
|
||||
def correlation_cycle(candles: np.ndarray, period=20, threshold=9, source_type="close", sequential=False) -> CC:
|
||||
def correlation_cycle(candles: np.ndarray, period: int = 20, threshold: int = 9, source_type: str = "close",
|
||||
sequential: bool = False) -> CC:
|
||||
"""
|
||||
"Correlation Cycle, Correlation Angle, Market State - John Ehlers
|
||||
|
||||
@@ -16,17 +20,32 @@ def correlation_cycle(candles: np.ndarray, period=20, threshold=9, source_type="
|
||||
: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)
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
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)
|
||||
|
||||
# Market State Function
|
||||
state = np.where(np.abs(angle - priorAngle) < threshold, np.where(angle >= 0.0, 1, np.where(angle < 0.0, -1, 0)), 0)
|
||||
|
||||
if sequential:
|
||||
return CC(realPart, imagPart, angle, state)
|
||||
else:
|
||||
return CC(realPart[-1], imagPart[-1], angle[-1], state[-1])
|
||||
|
||||
|
||||
@njit
|
||||
def go_fast(source, period): # Function is compiled to machine code when called the first time
|
||||
# Correlation Cycle Function
|
||||
PIx2 = 4.0 * math.asin(1.0)
|
||||
PIx2 = 4.0 * np.arcsin(1.0)
|
||||
period = max(2, period)
|
||||
|
||||
realPart = np.full_like(source, np.nan)
|
||||
@@ -46,45 +65,34 @@ def correlation_cycle(candles: np.ndarray, period=20, threshold=9, source_type="
|
||||
|
||||
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
|
||||
HALF_OF_PI = math.asin(1.0)
|
||||
HALF_OF_PI = np.arcsin(1.0)
|
||||
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)
|
||||
priorAngle = np_shift(angle, 1, fill_value=np.nan)
|
||||
angle = np.where(np.logical_and(priorAngle > angle, priorAngle - angle < 270.0), priorAngle, angle)
|
||||
|
||||
# Market State Function
|
||||
state = np.where(np.abs(angle - priorAngle) < threshold, np.where(angle >= 0.0, 1, np.where(angle < 0.0, -1, 0)), 0)
|
||||
|
||||
if sequential:
|
||||
return CC(realPart, imagPart, angle, state)
|
||||
else:
|
||||
return CC(realPart[-1], imagPart[-1], angle[-1], state[-1])
|
||||
return realPart, imagPart, angle
|
||||
|
||||
@@ -3,20 +3,21 @@ from typing import Union
|
||||
import numpy as np
|
||||
import tulipy as ti
|
||||
|
||||
from jesse.helpers import slice_candles, same_length
|
||||
|
||||
def cvi(candles: np.ndarray, period=5, sequential=False) -> Union[float, np.ndarray]:
|
||||
|
||||
def cvi(candles: np.ndarray, period: int = 5, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
CVI - Chaikins Volatility
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 5
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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
|
||||
@@ -1,49 +1,61 @@
|
||||
import talib
|
||||
from collections import namedtuple
|
||||
|
||||
import numpy as np
|
||||
import talib
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
DamianiVolatmeter = namedtuple('DamianiVolatmeter', ['vol', 'anti' ])
|
||||
DamianiVolatmeter = namedtuple('DamianiVolatmeter', ['vol', 'anti'])
|
||||
|
||||
|
||||
def damiani_volatmeter(candles: np.ndarray, vis_atr=13, vis_std=20, sed_atr=40, sed_std=100, threshold=1.4, source_type="close", sequential=False) -> DamianiVolatmeter:
|
||||
def damiani_volatmeter(candles: np.ndarray, vis_atr: int = 13, vis_std: int = 20, sed_atr: int = 40, sed_std: int = 100,
|
||||
threshold: float = 1.4, source_type: str = "close",
|
||||
sequential: bool = False) -> DamianiVolatmeter:
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
lag_s = 0.5
|
||||
|
||||
vol = np.full_like(source, 0)
|
||||
t = np.full_like(source, 0)
|
||||
|
||||
atrvis = talib.ATR(candles[:, 3], candles[:, 4], candles[:, 2], timeperiod=vis_atr)
|
||||
atrsed = talib.ATR(candles[:, 3], candles[:, 4], candles[:, 2], timeperiod=sed_atr)
|
||||
|
||||
for i in range(source.shape[0]):
|
||||
if not (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
|
||||
vol, t = damiani_volatmeter_fast(source, sed_std, atrvis, atrsed, vis_std, threshold)
|
||||
|
||||
if sequential:
|
||||
return DamianiVolatmeter(vol, t)
|
||||
else:
|
||||
return DamianiVolatmeter(vol[-1], t[-1])
|
||||
|
||||
|
||||
@njit
|
||||
def damiani_volatmeter_fast(source, sed_std, atrvis, atrsed, vis_std,
|
||||
threshold): # Function is compiled to machine code when called the first time
|
||||
lag_s = 0.5
|
||||
|
||||
vol = np.full_like(source, 0)
|
||||
t = np.full_like(source, 0)
|
||||
for i in range(source.shape[0]):
|
||||
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
|
||||
return vol, t
|
||||
|
||||
@@ -2,46 +2,31 @@ from typing import Union
|
||||
|
||||
import numpy as np
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
from .high_pass_2_pole import high_pass_2_pole_fast
|
||||
|
||||
|
||||
def dec_osc(candles: np.ndarray, hp_period=125, k=1, source_type="close", sequential=False) -> Union[float, np.ndarray]:
|
||||
def dec_osc(candles: np.ndarray, hp_period: int = 125, k: float = 1, source_type: str = "close",
|
||||
sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
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
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
alphaArg1 = 2 * np.pi * 0.707 / hp_period
|
||||
alpha1 = (np.cos(alphaArg1) + np.sin(alphaArg1) - 1) / np.cos(alphaArg1)
|
||||
coeff1 = np.array([(1 - alpha1 / 2) ** 2, 2 * (1 - alpha1), -(1 - alpha1) ** 2])
|
||||
hp1 = np.copy(source)
|
||||
|
||||
alphaArg2 = 2 * np.pi * 0.707 / (0.5 * hp_period)
|
||||
alpha2 = (np.cos(alphaArg2) + np.sin(alphaArg2) - 1) / np.cos(alphaArg2)
|
||||
coeff2 = np.array([(1 - alpha2 / 2) ** 2, 2 * (1 - alpha2), -(1 - alpha2) ** 2])
|
||||
hp2 = np.copy(source)
|
||||
hp = high_pass_2_pole_fast(source, hp_period)
|
||||
dec = source - hp
|
||||
decosc = high_pass_2_pole_fast(dec, 0.5 * hp_period)
|
||||
|
||||
for i in range(source.shape[0]):
|
||||
val1 = np.array([source[i] - 2 * source[i - 1] + source[i - 2], hp1[i - 1], hp1[i - 2]])
|
||||
hp1[i] = np.matmul(coeff1, val1)
|
||||
res = 100 * k * decosc / source
|
||||
|
||||
val2 = np.array(
|
||||
[(source[i] - hp1[i]) - 2 * (source[i - 1] - hp1[i - 1]) + (source[i - 2] - hp1[i - 2]), hp2[i - 1],
|
||||
hp2[i - 2]])
|
||||
hp2[i] = np.matmul(coeff2, val2)
|
||||
|
||||
res = 100 * k * hp2 / source
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -2,35 +2,26 @@ from typing import Union
|
||||
|
||||
import numpy as np
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
from .high_pass_2_pole import high_pass_2_pole_fast
|
||||
|
||||
|
||||
def decycler(candles: np.ndarray, hp_period=125, source_type="close", sequential=False) -> Union[float, np.ndarray]:
|
||||
def decycler(candles: np.ndarray, hp_period: int = 125, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
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
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
alphaArg1 = 2 * np.pi * 0.707 / hp_period
|
||||
alpha1 = (np.cos(alphaArg1) + np.sin(alphaArg1) - 1) / np.cos(alphaArg1)
|
||||
coeff1 = np.array([(1 - alpha1 / 2) ** 2, 2 * (1 - alpha1), -(1 - alpha1) ** 2])
|
||||
hp1 = np.copy(source)
|
||||
hp = high_pass_2_pole_fast(source, hp_period)
|
||||
res = source - hp
|
||||
|
||||
for i in range(source.shape[0]):
|
||||
val1 = np.array([source[i] - 2 * source[i - 1] + source[i - 2], hp1[i - 1], hp1[i - 2]])
|
||||
hp1[i] = np.matmul(coeff1, val1)
|
||||
|
||||
res = source - hp1
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -3,24 +3,27 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
|
||||
def dema(candles: np.ndarray, period=30, source_type="close", sequential=False) -> Union[float, np.ndarray]:
|
||||
def dema(candles: np.ndarray, period: int = 30, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
DEMA - Double Exponential Moving Average
|
||||
|
||||
: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
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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]
|
||||
|
||||
45
jesse/indicators/devstop.py
Normal file
45
jesse/indicators/devstop.py
Normal file
@@ -0,0 +1,45 @@
|
||||
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 slice_candles
|
||||
|
||||
|
||||
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 devtype: int - default: 0
|
||||
:param direction: str - default: long
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
high = candles[:, 3]
|
||||
low = candles[:, 4]
|
||||
|
||||
AVTR = talib.SMA(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)
|
||||
|
||||
return res if sequential else res[-1]
|
||||
@@ -3,21 +3,22 @@ from collections import namedtuple
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
DI = namedtuple('DI', ['plus', 'minus'])
|
||||
|
||||
|
||||
def di(candles: np.ndarray, period=14, sequential=False) -> DI:
|
||||
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)
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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,24 +3,25 @@ from collections import namedtuple
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
DM = namedtuple('DM', ['plus', 'minus'])
|
||||
|
||||
|
||||
def dm(candles: np.ndarray, period=14, sequential=False) -> DM:
|
||||
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)
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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)
|
||||
MINUS_DI = talib.MINUS_DM(candles[:, 3], candles[:, 4], timeperiod=period)
|
||||
PLUS_DI = talib.PLUS_DM(candles[:, 3], candles[:, 4], timeperiod=period)
|
||||
|
||||
if sequential:
|
||||
return DM(PLUS_DI, MINUS_DI)
|
||||
|
||||
@@ -3,21 +3,22 @@ from collections import namedtuple
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
DonchianChannel = namedtuple('DonchianChannel', ['upperband', 'middleband', 'lowerband'])
|
||||
|
||||
|
||||
def donchian(candles: np.ndarray, period=20, sequential=False) -> DonchianChannel:
|
||||
def donchian(candles: np.ndarray, period: int = 20, sequential: bool = False) -> DonchianChannel:
|
||||
"""
|
||||
Donchian Channels
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 20
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: DonchianChannel(upperband, middleband, lowerband)
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
UC = talib.MAX(candles[:, 3], timeperiod=period)
|
||||
LC = talib.MIN(candles[:, 4], timeperiod=period)
|
||||
|
||||
@@ -3,24 +3,24 @@ from typing import Union
|
||||
import numpy as np
|
||||
import tulipy as ti
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_candle_source, same_length, slice_candles
|
||||
|
||||
|
||||
def dpo(candles: np.ndarray, period=5, source_type="close", sequential=False) -> Union[float, np.ndarray]:
|
||||
def dpo(candles: np.ndarray, period: int = 5, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
DPO - Detrended Price Oscillator
|
||||
|
||||
: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
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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,22 +4,23 @@ import numpy as np
|
||||
import talib
|
||||
|
||||
import jesse.helpers as jh
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def dti(candles: np.ndarray, r=14, s=10, u=5, sequential=False) -> Union[float, np.ndarray]:
|
||||
def dti(candles: np.ndarray, r: int = 14, s: int = 10, u: int = 5, sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
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
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
high = candles[:, 3]
|
||||
low = candles[:, 4]
|
||||
|
||||
@@ -3,20 +3,20 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def dx(candles: np.ndarray, period=14, sequential=False) -> Union[float, np.ndarray]:
|
||||
def dx(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
DX - Directional Movement Index
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 14
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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,31 +2,41 @@ from typing import Union
|
||||
|
||||
import numpy as np
|
||||
import talib
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_candle_source, same_length, slice_candles
|
||||
|
||||
|
||||
def efi(candles: np.ndarray, period=13, source_type="close", sequential=False) -> Union[float, np.ndarray]:
|
||||
def efi(candles: np.ndarray, period: int = 13, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
EFI - Elders Force Index
|
||||
|
||||
: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
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
dif = np.zeros(len(source) - 1)
|
||||
for i in range(1, len(source)):
|
||||
dif[i - 1] = (source[i] - source[i - 1]) * candles[:, 5][i]
|
||||
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(source.size - 1)
|
||||
for i in range(1, source.size):
|
||||
dif[i - 1] = (source[i] - source[i - 1]) * volume[i]
|
||||
return dif
|
||||
|
||||
@@ -3,24 +3,27 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
|
||||
def ema(candles: np.ndarray, period=5, source_type="close", sequential=False) -> Union[float, np.ndarray]:
|
||||
def ema(candles: np.ndarray, period: int = 5, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
EMA - Exponential Moving Average
|
||||
|
||||
: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
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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]
|
||||
|
||||
@@ -1,32 +1,53 @@
|
||||
import math
|
||||
from collections import namedtuple
|
||||
|
||||
import numpy as np
|
||||
import talib
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
EMD = namedtuple('EMD', ['upperband', 'middleband', 'lowerband'])
|
||||
|
||||
|
||||
def emd(candles: np.ndarray, period=20, delta=0.5, fraction=0.1, sequential=False) -> EMD:
|
||||
def emd(candles: np.ndarray, period: int = 20, delta=0.5, fraction=0.1, sequential: bool = False) -> EMD:
|
||||
"""
|
||||
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)
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
price = (candles[:, 3] + candles[:, 4]) / 2
|
||||
|
||||
bp = bp_fast(price, period, delta)
|
||||
|
||||
mean = talib.SMA(bp, timeperiod=2 * period)
|
||||
peak, valley = peak_valley_fast(bp, price)
|
||||
|
||||
avg_peak = fraction * talib.SMA(peak, timeperiod=50)
|
||||
avg_valley = fraction * talib.SMA(valley, timeperiod=50)
|
||||
|
||||
if sequential:
|
||||
return EMD(avg_peak, mean, avg_valley)
|
||||
else:
|
||||
return EMD(avg_peak[-1], mean[-1], avg_valley[-1])
|
||||
|
||||
|
||||
@njit
|
||||
def bp_fast(price, period, delta):
|
||||
# bandpass filter
|
||||
beta = math.cos(2 * math.pi / period)
|
||||
gamma = 1 / math.cos(4 * math.pi * delta / period)
|
||||
alpha = gamma - math.sqrt(gamma * gamma - 1)
|
||||
beta = np.cos(2 * np.pi / period)
|
||||
gamma = 1 / np.cos(4 * np.pi * delta / period)
|
||||
alpha = gamma - np.sqrt(gamma * gamma - 1)
|
||||
bp = np.zeros_like(price)
|
||||
|
||||
for i in range(price.shape[0]):
|
||||
@@ -34,9 +55,11 @@ def emd(candles: np.ndarray, period=20, delta=0.5, fraction=0.1, sequential=Fals
|
||||
bp[i] = 0.5 * (1 - alpha) * (price[i] - price[i - 2]) + beta * (1 + alpha) * bp[i - 1] - alpha * bp[i - 2]
|
||||
else:
|
||||
bp[i] = 0.5 * (1 - alpha) * (price[i] - price[i - 2])
|
||||
return bp
|
||||
|
||||
mean = talib.SMA(bp, timeperiod=2 * period)
|
||||
|
||||
@njit
|
||||
def peak_valley_fast(bp, price):
|
||||
peak = np.copy(bp)
|
||||
valley = np.copy(bp)
|
||||
|
||||
@@ -49,10 +72,4 @@ def emd(candles: np.ndarray, period=20, delta=0.5, fraction=0.1, sequential=Fals
|
||||
if bp[i - 1] < bp[i] and bp[i - 1] < bp[i - 2]:
|
||||
valley[i] = bp[i - 1]
|
||||
|
||||
avg_peak = fraction * talib.SMA(peak, timeperiod=50)
|
||||
avg_valley = fraction * talib.SMA(valley, timeperiod=50)
|
||||
|
||||
if sequential:
|
||||
return EMD(avg_peak, mean, avg_valley)
|
||||
else:
|
||||
return EMD(avg_peak[-1], mean[-1], avg_valley[-1])
|
||||
return peak, valley
|
||||
|
||||
@@ -3,20 +3,21 @@ from typing import Union
|
||||
import numpy as np
|
||||
import tulipy as ti
|
||||
|
||||
from jesse.helpers import slice_candles, same_length
|
||||
|
||||
|
||||
def emv(candles: np.ndarray, sequential=False) -> Union[float, np.ndarray]:
|
||||
def emv(candles: np.ndarray, sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
EMV - Ease of Movement
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
res = ti.emv(np.ascontiguousarray(candles[:, 3]), np.ascontiguousarray(candles[:, 4]), np.ascontiguousarray(candles[:, 5]))
|
||||
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
|
||||
32
jesse/indicators/er.py
Normal file
32
jesse/indicators/er.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
from numpy.lib.stride_tricks import sliding_window_view
|
||||
|
||||
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[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
ER - The Kaufman Efficiency indicator
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 5
|
||||
: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)
|
||||
|
||||
change = np.abs(np.diff(source, period))
|
||||
abs_dif = np.abs(np.diff(source))
|
||||
swv = sliding_window_view(abs_dif, window_shape=period)
|
||||
volatility = swv.sum()
|
||||
|
||||
res = change / volatility
|
||||
|
||||
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,28 +3,27 @@ from collections import namedtuple
|
||||
import numpy as np
|
||||
import tulipy as ti
|
||||
|
||||
from jesse.helpers import slice_candles, same_length
|
||||
|
||||
FisherTransform = namedtuple('FisherTransform', ['fisher', 'signal'])
|
||||
|
||||
|
||||
def fisher(candles: np.ndarray, period=9, sequential=False) -> FisherTransform:
|
||||
def fisher(candles: np.ndarray, period: int = 9, sequential: bool = False) -> FisherTransform:
|
||||
"""
|
||||
The Fisher Transform helps identify price reversals.
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 9
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: FisherTransform(fisher, signal)
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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,24 +3,24 @@ from typing import Union
|
||||
import numpy as np
|
||||
import tulipy as ti
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_candle_source, same_length, slice_candles
|
||||
|
||||
|
||||
def fosc(candles: np.ndarray, period=5, source_type="close", sequential=False) -> Union[float, np.ndarray]:
|
||||
def fosc(candles: np.ndarray, period: int = 5, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
FOSC - Forecast Oscillator
|
||||
|
||||
: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
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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,10 +1,16 @@
|
||||
import math
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def frama(candles: np.ndarray, window=10, FC=1, SC=300, sequential=False) -> Union[float, np.ndarray]:
|
||||
def frama(candles: np.ndarray, window: int = 10, FC: int = 1, SC: int = 300, sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
Fractal Adaptive Moving Average (FRAMA)
|
||||
|
||||
@@ -12,13 +18,11 @@ def frama(candles: np.ndarray, window=10, FC=1, SC=300, sequential=False) -> Uni
|
||||
: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
|
||||
"""
|
||||
|
||||
if len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
n = window
|
||||
|
||||
@@ -27,35 +31,43 @@ def frama(candles: np.ndarray, window=10, FC=1, SC=300, sequential=False) -> Uni
|
||||
print("FRAMA n must be even. Adding one")
|
||||
n += 1
|
||||
|
||||
w = math.log(2.0 / (SC + 1))
|
||||
res = frame_fast(candles, n, SC, FC)
|
||||
|
||||
D = np.zeros(len(candles))
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return res[-1]
|
||||
|
||||
|
||||
@njit
|
||||
def frame_fast(candles, n, SC, FC):
|
||||
w = np.log(2.0 / (SC + 1))
|
||||
|
||||
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]
|
||||
|
||||
# take 2 batches of the input
|
||||
split = np.split(per, 2)
|
||||
v1 = split[0]
|
||||
v2 = split[1]
|
||||
v1 = per[per.shape[0] // 2:]
|
||||
v2 = per[:per.shape[0] // 2]
|
||||
|
||||
N1 = (np.max(v1[:, 3]) - np.min(v1[:, 4])) / (n / 2)
|
||||
N2 = (np.max(v2[:, 3]) - np.min(v2[:, 4])) / (n / 2)
|
||||
N3 = (np.max(per[:, 3]) - np.min(per[:, 4])) / n
|
||||
N1 = (max(v1[:, 3]) - min(v1[:, 4])) / (n / 2)
|
||||
N2 = (max(v2[:, 3]) - min(v2[:, 4])) / (n / 2)
|
||||
N3 = (max(per[:, 3]) - min(per[:, 4])) / n
|
||||
|
||||
if N1 > 0 and N2 > 0 and N3 > 0:
|
||||
D[i] = (math.log(N1 + N2) - math.log(N3)) / math.log(2)
|
||||
D[i] = (np.log(N1 + N2) - np.log(N3)) / np.log(2)
|
||||
else:
|
||||
D[i] = D[i - 1]
|
||||
|
||||
oldalpha = math.exp(w * (D[i] - 1))
|
||||
oldalpha = np.exp(w * (D[i] - 1))
|
||||
# keep btwn 1 & 0.01
|
||||
oldalpha = np.max([oldalpha, 0.1])
|
||||
oldalpha = np.min([oldalpha, 1])
|
||||
oldalpha = max([oldalpha, 0.1])
|
||||
oldalpha = min([oldalpha, 1])
|
||||
|
||||
oldN = (2 - oldalpha) / oldalpha
|
||||
N = ((SC - FC) * ((oldN - 1) / (SC - 1))) + FC
|
||||
@@ -67,14 +79,10 @@ def frama(candles: np.ndarray, window=10, FC=1, SC=300, sequential=False) -> Uni
|
||||
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]
|
||||
|
||||
if sequential:
|
||||
return frama
|
||||
else:
|
||||
return frama[-1]
|
||||
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
|
||||
|
||||
54
jesse/indicators/fwma.py
Normal file
54
jesse/indicators/fwma.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from math import fabs
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
from numpy.lib.stride_tricks import sliding_window_view
|
||||
|
||||
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[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
Fibonacci's Weighted Moving Average (FWMA)
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 5
|
||||
: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)
|
||||
|
||||
fibs = fibonacci(n=period)
|
||||
swv = sliding_window_view(source, window_shape=period)
|
||||
res = np.average(swv, weights=fibs, axis=-1)
|
||||
|
||||
return same_length(candles, res) if sequential else res[-1]
|
||||
|
||||
|
||||
def fibonacci(n: int = 2) -> np.ndarray:
|
||||
"""Fibonacci Sequence as a numpy array"""
|
||||
n = int(fabs(n)) if n >= 0 else 2
|
||||
|
||||
n -= 1
|
||||
a, b = 1, 1
|
||||
|
||||
result = np.array([a])
|
||||
|
||||
for _ in range(n):
|
||||
a, b = b, a + b
|
||||
result = np.append(result, a)
|
||||
|
||||
fib_sum = np.sum(result)
|
||||
if fib_sum > 0:
|
||||
return result / fib_sum
|
||||
else:
|
||||
return result
|
||||
@@ -4,23 +4,23 @@ import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source, np_shift
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
GATOR = namedtuple('GATOR', ['upper', 'lower', 'upper_change', 'lower_change'])
|
||||
|
||||
|
||||
def gatorosc(candles: np.ndarray, source_type="close", sequential=False) -> GATOR:
|
||||
def gatorosc(candles: np.ndarray, source_type: str = "close", sequential: bool = False) -> GATOR:
|
||||
"""
|
||||
Gator Oscillator by Bill M. Williams
|
||||
|
||||
: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)
|
||||
"""
|
||||
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
@@ -39,6 +39,7 @@ def gatorosc(candles: np.ndarray, source_type="close", sequential=False) -> GATO
|
||||
else:
|
||||
return GATOR(upper[-1], lower[-1], upper_change[-1], lower_change[-1])
|
||||
|
||||
|
||||
def numpy_ewma(data, window):
|
||||
"""
|
||||
|
||||
@@ -47,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,36 +1,54 @@
|
||||
import math
|
||||
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
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
|
||||
def gauss(candles: np.ndarray, period=14, poles=4, source_type="close", sequential=False) -> Union[float, np.ndarray]:
|
||||
def gauss(candles: np.ndarray, period: int = 14, poles: int = 4, source_type: str = "close",
|
||||
sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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)
|
||||
N = len(source)
|
||||
fil, to_fill = gauss_fast(source, period, poles)
|
||||
|
||||
if to_fill != 0:
|
||||
res = np.insert(fil[poles:], 0, np.repeat(np.nan, to_fill))
|
||||
else:
|
||||
res = fil[poles:]
|
||||
|
||||
return res if sequential else res[-1]
|
||||
|
||||
|
||||
@njit
|
||||
def gauss_fast(source, period, poles):
|
||||
N = source.size
|
||||
source = source[~np.isnan(source)]
|
||||
to_fill = N - len(source)
|
||||
PI = math.pi
|
||||
beta = (1 - math.cos(2 * PI / period)) / (math.pow(2, 1 / poles) - 1)
|
||||
alpha = -beta + math.sqrt(math.pow(beta, 2) + 2 * beta)
|
||||
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:
|
||||
@@ -40,7 +58,7 @@ def gauss(candles: np.ndarray, period=14, poles=4, source_type="close", sequenti
|
||||
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:
|
||||
@@ -52,12 +70,4 @@ def gauss(candles: np.ndarray, period=14, poles=4, source_type="close", sequenti
|
||||
|
||||
fil[poles + i] = np.dot(coeff, val)
|
||||
|
||||
if to_fill != 0:
|
||||
res = np.insert(fil[poles:], 0, np.repeat(np.nan, to_fill))
|
||||
else:
|
||||
res = fil[poles:]
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
return fil, to_fill
|
||||
|
||||
@@ -1,37 +1,47 @@
|
||||
import math
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
from jesse.helpers import get_candle_source
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
|
||||
|
||||
def high_pass(candles: np.ndarray, period=48, source_type="close", sequential=False) -> Union[float, np.ndarray]:
|
||||
def high_pass(candles: np.ndarray, period: int = 48, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
High Pass Filter indicator by John F. Ehlers
|
||||
(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
|
||||
"""
|
||||
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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)
|
||||
|
||||
hpf = np.full_like(source, 0)
|
||||
|
||||
for i in range(source.shape[0]):
|
||||
if not (i < 2):
|
||||
alpha_arg = 2 * math.pi / (period * 1.414)
|
||||
alpha1 = (math.cos(alpha_arg) + math.sin(alpha_arg) - 1) / math.cos(alpha_arg)
|
||||
hpf[i] = math.pow(1.0 - alpha1 / 2.0, 2) * (source[i] - 2 * source[i - 1] + source[i - 2]) + 2 * (1 - alpha1) * hpf[i - 1] - math.pow(1 - alpha1, 2) * hpf[i - 2]
|
||||
hpf = high_pass_fast(source, period)
|
||||
|
||||
if sequential:
|
||||
return hpf
|
||||
else:
|
||||
return None if np.isnan(hpf[-1]) else hpf[-1]
|
||||
|
||||
|
||||
@njit
|
||||
def high_pass_fast(source, period): # Function is compiled to machine code when called the first time
|
||||
k = 1
|
||||
alpha = 1 + (np.sin(2 * np.pi * k / period) - 1) / np.cos(2 * np.pi * k / period)
|
||||
newseries = np.copy(source)
|
||||
for i in range(1, source.shape[0]):
|
||||
newseries[i] = (1 - alpha / 2) * source[i] - (1 - alpha / 2) * source[i - 1] \
|
||||
+ (1 - alpha) * newseries[i - 1]
|
||||
return newseries
|
||||
|
||||
49
jesse/indicators/high_pass_2_pole.py
Normal file
49
jesse/indicators/high_pass_2_pole.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 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 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)
|
||||
|
||||
hpf = high_pass_2_pole_fast(source, period)
|
||||
|
||||
if sequential:
|
||||
return hpf
|
||||
else:
|
||||
return None if np.isnan(hpf[-1]) else hpf[-1]
|
||||
|
||||
|
||||
@njit
|
||||
def high_pass_2_pole_fast(source, period, K=0.707): # Function is compiled to machine code when called the first time
|
||||
alpha = 1 + (np.sin(2 * np.pi * K / period) - 1) / np.cos(2 * np.pi * K / period)
|
||||
newseries = np.copy(source)
|
||||
for i in range(2, source.shape[0]):
|
||||
newseries[i] = (1 - alpha / 2) ** 2 * source[i] \
|
||||
- 2 * (1 - alpha / 2) ** 2 * source[i - 1] \
|
||||
+ (1 - alpha / 2) ** 2 * source[i - 2] \
|
||||
+ 2 * (1 - alpha) * newseries[i - 1] - (1 - alpha) ** 2 * newseries[i - 2]
|
||||
return newseries
|
||||
@@ -3,24 +3,28 @@ from typing import Union
|
||||
import numpy as np
|
||||
import tulipy as ti
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_candle_source, same_length, slice_candles
|
||||
|
||||
|
||||
def hma(candles: np.ndarray, period=5, source_type="close", sequential=False) -> Union[float, np.ndarray]:
|
||||
def hma(candles: np.ndarray, period: int = 5, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
Hull Moving Average
|
||||
|
||||
: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
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
# 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,20 +4,20 @@ import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def ht_dcperiod(candles: np.ndarray, source_type="close", sequential=False) -> Union[float, np.ndarray]:
|
||||
def ht_dcperiod(candles: np.ndarray, source_type: str = "close", sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
HT_DCPERIOD - Hilbert Transform - Dominant Cycle Period
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
res = talib.HT_DCPERIOD(source)
|
||||
|
||||
@@ -4,22 +4,22 @@ import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def ht_dcphase(candles: np.ndarray, source_type="close", sequential=False) -> Union[float, np.ndarray]:
|
||||
def ht_dcphase(candles: np.ndarray, source_type: str = "close", sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
HT_DCPHASE - Hilbert Transform - Dominant Cycle Phase
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
res = talib.HT_DCPHASE (source)
|
||||
res = talib.HT_DCPHASE(source)
|
||||
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -4,21 +4,22 @@ import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
IQ = namedtuple('IQ', ['inphase', 'quadrature'])
|
||||
|
||||
def ht_phasor(candles: np.ndarray, source_type="close", sequential=False) -> IQ:
|
||||
|
||||
def ht_phasor(candles: np.ndarray, source_type: str = "close", sequential: bool = False) -> IQ:
|
||||
"""
|
||||
HT_PHASOR - Hilbert Transform - Phasor Components
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: IQ(inphase, quadrature)
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
inphase, quadrature = talib.HT_PHASOR(source)
|
||||
|
||||
@@ -4,21 +4,22 @@ import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
SINEWAVE = namedtuple('SINEWAVE', ['sine', 'lead'])
|
||||
|
||||
def ht_sine(candles: np.ndarray, source_type="close", sequential=False) -> SINEWAVE:
|
||||
|
||||
def ht_sine(candles: np.ndarray, source_type: str = "close", sequential: bool = False) -> SINEWAVE:
|
||||
"""
|
||||
HT_SINE - Hilbert Transform - SineWave
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: SINEWAVE(sine, lead)
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
sine, leadsine = talib.HT_SINE(source)
|
||||
|
||||
@@ -4,22 +4,25 @@ import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def ht_trendline(candles: np.ndarray, source_type="close", sequential=False) -> Union[float, np.ndarray]:
|
||||
def ht_trendline(candles: np.ndarray, source_type: str = "close", sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
HT_TRENDLINE - Hilbert Transform - Instantaneous Trendline
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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,20 +4,20 @@ import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def ht_trendmode(candles: np.ndarray, source_type="close", sequential=False) -> Union[float, np.ndarray]:
|
||||
def ht_trendmode(candles: np.ndarray, source_type: str = "close", sequential: bool = False) -> Union[float, np.ndarray]:
|
||||
"""
|
||||
HT_TRENDMODE - Hilbert Transform - Trend vs Cycle Mode
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default=False
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: int | np.ndarray
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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
|
||||
@@ -5,23 +5,23 @@ import numpy as np
|
||||
IchimokuCloud = namedtuple('IchimokuCloud', ['conversion_line', 'base_line', 'span_a', 'span_b'])
|
||||
|
||||
|
||||
def ichimoku_cloud(candles: np.ndarray, conversion_line_period=9, base_line_period=26, lagging_line_period=52,
|
||||
displacement=26) -> IchimokuCloud:
|
||||
def ichimoku_cloud(candles: np.ndarray, conversion_line_period: int = 9, base_line_period: int = 26,
|
||||
lagging_line_period: int = 52, displacement: int = 26) -> IchimokuCloud:
|
||||
"""
|
||||
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
|
||||
|
||||
@@ -4,48 +4,40 @@ import numpy as np
|
||||
import talib
|
||||
|
||||
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',
|
||||
'future_span_b'])
|
||||
|
||||
|
||||
def ichimoku_cloud_seq(candles: np.ndarray, conversion_line_period=9, base_line_period=26, lagging_line_period=52,
|
||||
displacement=26, sequential=False) -> IchimokuCloud:
|
||||
def ichimoku_cloud_seq(candles: np.ndarray, conversion_line_period: int = 9, base_line_period: int = 26,
|
||||
lagging_line_period: int = 52, displacement: int = 26,
|
||||
sequential: bool = False) -> IchimokuCloud:
|
||||
"""
|
||||
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.")
|
||||
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
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:
|
||||
@@ -53,3 +45,8 @@ def ichimoku_cloud_seq(candles: np.ndarray, conversion_line_period=9, base_line_
|
||||
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,45 +1,53 @@
|
||||
from collections import namedtuple
|
||||
|
||||
import numpy as np
|
||||
try:
|
||||
from numba import njit
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
ITREND = namedtuple('ITREND', ['signal', 'it', 'trigger'])
|
||||
|
||||
|
||||
def itrend(candles: np.ndarray, alpha=0.07, source_type="hl2", sequential=False) -> ITREND:
|
||||
def itrend(candles: np.ndarray, alpha: float = 0.07, source_type: str = "hl2", sequential: bool = False) -> ITREND:
|
||||
"""
|
||||
Instantaneous Trendline
|
||||
|
||||
: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)
|
||||
"""
|
||||
if not sequential and len(candles) > 240:
|
||||
candles = candles[-240:]
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
coeff = np.array(
|
||||
[(alpha - alpha ** 2 / 4), alpha ** 2 / 2, - (alpha - alpha ** 2 * 3 / 4), 2 * (1 - alpha), - (1 - alpha) ** 2])
|
||||
signal, it, trigger = itrend_fast(source, alpha)
|
||||
|
||||
if sequential:
|
||||
return ITREND(signal, it, trigger)
|
||||
else:
|
||||
return ITREND(signal[-1], it[-1], trigger[-1])
|
||||
|
||||
|
||||
@njit
|
||||
def itrend_fast(source, alpha):
|
||||
it = np.copy(source)
|
||||
for i in range(2, 7):
|
||||
it[i] = (source[i] + 2 * source[i - 1] + source[i - 2]) / 4
|
||||
for i in range(7, source.shape[0]):
|
||||
val = np.array([source[i], source[i - 1], source[i - 2], it[i - 1], it[i - 2]])
|
||||
it[i] = np.matmul(coeff, val)
|
||||
it[i] = (alpha - alpha ** 2 / 4) * source[i] \
|
||||
+ alpha ** 2 / 2 * source[i - 1] \
|
||||
- (alpha - alpha ** 2 * 3 / 4) * source[i - 2] \
|
||||
+ 2 * (1 - alpha) * it[i - 1] - (1 - alpha) ** 2 * it[i - 2]
|
||||
|
||||
# compute lead 2 trigger & signal
|
||||
lag2 = np.roll(it, 20)
|
||||
lag2[:20] = it[:20]
|
||||
trigger = 2 * it - lag2
|
||||
signal = (trigger > it) * 1 - (trigger < it) * 1
|
||||
|
||||
if sequential:
|
||||
return ITREND(signal, it, trigger)
|
||||
else:
|
||||
return ITREND(signal[-1], it[-1], trigger[-1])
|
||||
return signal, it, trigger
|
||||
|
||||
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]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user