diff --git a/testy/ascending.py b/testy/ascending.py new file mode 100644 index 0000000..3980cd9 --- /dev/null +++ b/testy/ascending.py @@ -0,0 +1,6 @@ +import json + +a = """[{"time":1699021800.019723,"value":28.325},{"time":1699021830.05652,"value":28.315},{"time":1699021860.305877,"value":28.31},{"time":1699021890.396815,"value":28.305},{"time":1699021920.767856,"value":28.295},{"time":1699021951.04426,"value":28.31},{"time":1699021982.011999,"value":28.32},{"time":1699022012.571608,"value":28.3},{"time":1699022042.970093,"value":28.32},{"time":1699022073.277922,"value":28.335},{"time":1699022103.336126,"value":28.3401},{"time":1699022134.041774,"value":28.33},{"time":1699022166.753206,"value":28.31},{"time":1699022197.699501,"value":28.305},{"time":1699022228.250299,"value":28.3033},{"time":1699022258.417441,"value":28.305},{"time":1699022289.977942,"value":28.285},{"time":1699022320.095033,"value":28.3168},{"time":1699022353.873671,"value":28.31},{"time":1699022385.189729,"value":28.3383},{"time":1699022415.544463,"value":28.345},{"time":1699022445.59419,"value":28.32},{"time":1699022476.329711,"value":28.33},{"time":1699022512.414328,"value":28.315},{"time":1699022542.768232,"value":28.32},{"time":1699022574.459283,"value":28.28},{"time":1699022606.940348,"value":28.295},{"time":1699022637.404558,"value":28.29},{"time":1699022667.65586,"value":28.305},{"time":1699022700.075183,"value":28.325},{"time":1699022730.456352,"value":28.32},{"time":1699022761.189056,"value":28.335},{"time":1699022791.503085,"value":28.34},{"time":1699022822.939517,"value":28.33},{"time":1699022853.489484,"value":28.355},{"time":1699022884.086032,"value":28.345},{"time":1699022915.034083,"value":28.3599},{"time":1699022945.090479,"value":28.345},{"time":1699022975.854524,"value":28.3501},{"time":1699023007.06636,"value":28.355},{"time":1699023037.257665,"value":28.379},{"time":1699023067.38282,"value":28.3775},{"time":1699023098.046355,"value":28.355},{"time":1699023129.624668,"value":28.343},{"time":1699023159.831437,"value":28.37},{"time":1699023191.562167,"value":28.3503},{"time":1699023222.129191,"value":28.315},{"time":1699023252.141027,"value":28.335},{"time":1699023282.732192,"value":28.325},{"time":1699023314.273008,"value":28.325},{"time":1699023344.476307,"value":28.3316},{"time":1699023375.128845,"value":28.38},{"time":1699023405.654461,"value":28.39},{"time":1699023436.298197,"value":28.395},{"time":1699023467.849952,"value":28.39},{"time":1699023498.607222,"value":28.4},{"time":1699023528.88649,"value":28.41},{"time":1699023559.248959,"value":28.405},{"time":1699023590.036323,"value":28.41},{"time":1699023620.057996,"value":28.43},{"time":1699023650.174218,"value":28.45},{"time":1699023680.550277,"value":28.445},{"time":1699023710.8486,"value":28.435},{"time":1699023742.003348,"value":28.425},{"time":1699023773.361168,"value":28.425},{"time":1699023803.73621,"value":28.4418},{"time":1699023835.004908,"value":28.425},{"time":1699023865.310641,"value":28.42},{"time":1699023895.496276,"value":28.44},{"time":1699023925.818644,"value":28.473},{"time":1699023957.306206,"value":28.4818},{"time":1699023987.438431,"value":28.46},{"time":1699024020.011762,"value":28.46},{"time":1699024050.197554,"value":28.45},{"time":1699024081.520005,"value":28.43},{"time":1699024114.582807,"value":28.445},{"time":1699024146.610304,"value":28.4301},{"time":1699024176.96372,"value":28.425},{"time":1699024209.39291,"value":28.4301},{"time":1699024241.215645,"value":28.42},{"time":1699024271.411628,"value":28.41},{"time":1699024301.419847,"value":28.41},{"time":1699024331.490853,"value":28.425},{"time":1699024361.964565,"value":28.43},{"time":1699024393.914504,"value":28.41},{"time":1699024423.945862,"value":28.405},{"time":1699024458.32594,"value":28.43},{"time":1699024489.974228,"value":28.425},{"time":1699024520.388356,"value":28.45},{"time":1699024550.743977,"value":28.46},{"time":1699024581.591713,"value":28.44},{"time":1699024611.606568,"value":28.41},{"time":1699024642.212998,"value":28.415},{"time":1699024674.862401,"value":28.435},{"time":1699024705.509538,"value":28.435},{"time":1699024735.983247,"value":28.45},{"time":1699024766.575683,"value":28.445},{"time":1699024797.695507,"value":28.445},{"time":1699024828.083169,"value":28.44},{"time":1699024858.758286,"value":28.45},{"time":1699024888.794182,"value":28.45},{"time":1699024919.277686,"value":28.44},{"time":1699024950.819448,"value":28.445},{"time":1699024980.977291,"value":28.455},{"time":1699025011.288962,"value":28.485},{"time":1699025041.837843,"value":28.4786},{"time":1699025072.220646,"value":28.49},{"time":1699025102.310644,"value":28.4899},{"time":1699025132.750789,"value":28.495},{"time":1699025163.385008,"value":28.515},{"time":1699025194.110701,"value":28.52},{"time":1699025225.140705,"value":28.53},{"time":1699025255.623126,"value":28.525},{"time":1699025286.32314,"value":28.525},{"time":1699025318.177313,"value":28.54},{"time":1699025348.351411,"value":28.52},{"time":1699025380.699046,"value":28.46},{"time":1699025411.317513,"value":28.46},{"time":1699025441.334031,"value":28.495},{"time":1699025477.224306,"value":28.51},{"time":1699025508.461651,"value":28.5},{"time":1699025539.933449,"value":28.495},{"time":1699025570.844385,"value":28.505},{"time":1699025601.852059,"value":28.485},{"time":1699025631.875163,"value":28.49},{"time":1699025664.199609,"value":28.48},{"time":1699025697.732327,"value":28.495},{"time":1699025728.404528,"value":28.49},{"time":1699025760.023252,"value":28.485},{"time":1699025791.299449,"value":28.485},{"time":1699025821.362548,"value":28.495},{"time":1699025853.135535,"value":28.49},{"time":1699025884.363134,"value":28.485},{"time":1699025914.712172,"value":28.475},{"time":1699025945.858376,"value":28.495},{"time":1699025979.710519,"value":28.51},{"time":1699026010.889225,"value":28.505},{"time":1699026043.756471,"value":28.505},{"time":1699026074.317655,"value":28.515},{"time":1699026104.755859,"value":28.505},{"time":1699026135.691582,"value":28.51},{"time":1699026165.941072,"value":28.5299},{"time":1699026196.243042,"value":28.515},{"time":1699026226.244767,"value":28.515},{"time":1699026256.991419,"value":28.53},{"time":1699026287.152732,"value":28.515},{"time":1699026317.249027,"value":28.5192},{"time":1699026348.015012,"value":28.52},{"time":1699026381.142881,"value":28.515},{"time":1699026412.066925,"value":28.53},{"time":1699026442.250968,"value":28.52},{"time":1699026475.363941,"value":28.535},{"time":1699026505.738539,"value":28.5},{"time":1699026535.932925,"value":28.4829},{"time":1699026567.468561,"value":28.49},{"time":1699026597.92468,"value":28.5},{"time":1699026628.0574,"value":28.51},{"time":1699026658.116916,"value":28.51},{"time":1699026690.27825,"value":28.515},{"time":1699026720.330602,"value":28.515},{"time":1699026751.926044,"value":28.5101},{"time":1699026782.80328,"value":28.53},{"time":1699026813.872945,"value":28.53},{"time":1699026843.911938,"value":28.535},{"time":1699026874.21488,"value":28.515},{"time":1699026904.435069,"value":28.53},{"time":1699026934.857691,"value":28.53},{"time":1699026965.892694,"value":28.5299},{"time":1699026996.17253,"value":28.5256},{"time":1699027026.770303,"value":28.525},{"time":1699027058.74448,"value":28.52},{"time":1699027088.784821,"value":28.53},{"time":1699027121.945087,"value":28.54},{"time":1699027152.051352,"value":28.53},{"time":1699027182.120519,"value":28.54},{"time":1699027212.641808,"value":28.535},{"time":1699027243.815996,"value":28.54},{"time":1699027274.439364,"value":28.51},{"time":1699027305.789608,"value":28.51},{"time":1699027336.811255,"value":28.49},{"time":1699027368.509487,"value":28.5101},{"time":1699027398.683574,"value":28.53},{"time":1699027429.065187,"value":28.51},{"time":1699027459.517296,"value":28.5031},{"time":1699027489.744654,"value":28.485},{"time":1699027520.114365,"value":28.4987},{"time":1699027550.612786,"value":28.49},{"time":1699027580.67624,"value":28.49},{"time":1699027611.978115,"value":28.4831},{"time":1699027650.886537,"value":28.47},{"time":1699027681.180903,"value":28.4691},{"time":1699027711.893932,"value":28.465},{"time":1699027742.449302,"value":28.48},{"time":1699027775.144211,"value":28.485},{"time":1699027805.996057,"value":28.4875},{"time":1699027836.365314,"value":28.49},{"time":1699027867.176529,"value":28.485},{"time":1699027897.730371,"value":28.495},{"time":1699027929.20047,"value":28.505},{"time":1699027960.400628,"value":28.505},{"time":1699027991.98,"value":28.485},{"time":1699028025.659939,"value":28.475},{"time":1699028058.974496,"value":28.485},{"time":1699028089.503743,"value":28.49},{"time":1699028122.582512,"value":28.485},{"time":1699028153.656545,"value":28.465},{"time":1699028183.8974,"value":28.46},{"time":1699028214.973744,"value":28.47},{"time":1699028245.796393,"value":28.485},{"time":1699028277.769018,"value":28.495},{"time":1699028308.429092,"value":28.4901},{"time":1699028338.880168,"value":28.49},{"time":1699028369.994881,"value":28.495},{"time":1699028401.792756,"value":28.49},{"time":1699028431.970538,"value":28.49},{"time":1699028463.715935,"value":28.47},{"time":1699028494.034909,"value":28.44},{"time":1699028524.570011,"value":28.465},{"time":1699028555.07556,"value":28.485},{"time":1699028585.549326,"value":28.485},{"time":1699028617.013388,"value":28.48},{"time":1699028647.801635,"value":28.475},{"time":1699028677.998473,"value":28.485},{"time":1699028712.12153,"value":28.4954},{"time":1699028743.984318,"value":28.48},{"time":1699028775.623592,"value":28.4748},{"time":1699028806.543861,"value":28.475},{"time":1699028837.050434,"value":28.475},{"time":1699028867.212235,"value":28.465},{"time":1699028897.591785,"value":28.465},{"time":1699028928.264984,"value":28.47},{"time":1699028959.867069,"value":28.475},{"time":1699028991.542011,"value":28.455},{"time":1699029022.549037,"value":28.45},{"time":1699029054.259436,"value":28.46},{"time":1699029084.545072,"value":28.465},{"time":1699029114.900542,"value":28.46},{"time":1699029146.620521,"value":28.46},{"time":1699029179.589725,"value":28.45},{"time":1699029209.826802,"value":28.445},{"time":1699029240.148804,"value":28.435},{"time":1699029271.583122,"value":28.44},{"time":1699029302.460706,"value":28.455},{"time":1699029333.333215,"value":28.455},{"time":1699029364.235944,"value":28.445},{"time":1699029395.488928,"value":28.47},{"time":1699029425.619811,"value":28.435},{"time":1699029456.659012,"value":28.435},{"time":1699029487.403637,"value":28.435},{"time":1699029517.525669,"value":28.435},{"time":1699029547.728824,"value":28.435},{"time":1699029579.479949,"value":28.435},{"time":1699029613.898446,"value":28.4269},{"time":1699029644.27319,"value":28.435},{"time":1699029675.770214,"value":28.425},{"time":1699029705.793104,"value":28.425},{"time":1699029739.392305,"value":28.43},{"time":1699029769.406754,"value":28.425},{"time":1699029800.904643,"value":28.41},{"time":1699029832.43415,"value":28.405},{"time":1699029865.048612,"value":28.42},{"time":1699029896.112643,"value":28.405},{"time":1699029926.712455,"value":28.4},{"time":1699029956.920044,"value":28.395},{"time":1699029987.476193,"value":28.4095},{"time":1699030019.914236,"value":28.425},{"time":1699030051.474218,"value":28.4199},{"time":1699030081.790332,"value":28.425},{"time":1699030113.279521,"value":28.405},{"time":1699030145.058974,"value":28.415},{"time":1699030175.437213,"value":28.42},{"time":1699030205.761275,"value":28.425},{"time":1699030238.219352,"value":28.42},{"time":1699030268.316424,"value":28.425},{"time":1699030298.587675,"value":28.44},{"time":1699030329.148758,"value":28.45},{"time":1699030359.443807,"value":28.455},{"time":1699030389.805798,"value":28.46},{"time":1699030419.920336,"value":28.455},{"time":1699030450.006731,"value":28.455},{"time":1699030480.423882,"value":28.46},{"time":1699030510.492008,"value":28.46},{"time":1699030541.499704,"value":28.46},{"time":1699030573.265158,"value":28.4699},{"time":1699030603.661525,"value":28.485},{"time":1699030634.046372,"value":28.485},{"time":1699030665.943711,"value":28.5099},{"time":1699030697.001381,"value":28.51},{"time":1699030727.215985,"value":28.53},{"time":1699030757.566023,"value":28.55},{"time":1699030788.235149,"value":28.565},{"time":1699030820.008994,"value":28.57},{"time":1699030851.303736,"value":28.5759},{"time":1699030881.666398,"value":28.58},{"time":1699030914.139462,"value":28.6},{"time":1699030945.988479,"value":28.605},{"time":1699030976.119158,"value":28.62},{"time":1699031007.780077,"value":28.61},{"time":1699031038.730324,"value":28.615},{"time":1699031070.374924,"value":28.635},{"time":1699031100.754679,"value":28.595},{"time":1699031132.23774,"value":28.577},{"time":1699031162.755814,"value":28.6},{"time":1699031196.045974,"value":28.595},{"time":1699031226.95054,"value":28.59},{"time":1699031259.33105,"value":28.575},{"time":1699031290.585698,"value":28.565},{"time":1699031321.58565,"value":28.56},{"time":1699031351.630394,"value":28.57},{"time":1699031382.064823,"value":28.565},{"time":1699031412.572971,"value":28.58},{"time":1699031443.50161,"value":28.5695},{"time":1699031474.43398,"value":28.555},{"time":1699031504.614992,"value":28.545},{"time":1699031535.93342,"value":28.55},{"time":1699031567.581363,"value":28.55},{"time":1699031598.169505,"value":28.56},{"time":1699031631.176986,"value":28.54},{"time":1699031663.701909,"value":28.545},{"time":1699031696.887383,"value":28.53},{"time":1699031728.596345,"value":28.545},{"time":1699031762.371156,"value":28.5599},{"time":1699031792.588913,"value":28.565},{"time":1699031823.316266,"value":28.58},{"time":1699031853.321177,"value":28.591},{"time":1699031883.417252,"value":28.585},{"time":1699031914.680392,"value":28.56},{"time":1699031946.898342,"value":28.5692},{"time":1699031977.632583,"value":28.565},{"time":1699032007.711922,"value":28.5599},{"time":1699032037.902385,"value":28.56},{"time":1699032070.087226,"value":28.565},{"time":1699032101.920787,"value":28.57},{"time":1699032132.408531,"value":28.57},{"time":1699032164.124163,"value":28.5785},{"time":1699032194.941612,"value":28.58},{"time":1699032225.926233,"value":28.565},{"time":1699032260.046657,"value":28.56},{"time":1699032290.968033,"value":28.565},{"time":1699032325.746135,"value":28.555},{"time":1699032359.734661,"value":28.547},{"time":1699032391.682556,"value":28.53},{"time":1699032423.44457,"value":28.55},{"time":1699032455.05434,"value":28.545},{"time":1699032487.116926,"value":28.52},{"time":1699032518.664223,"value":28.5001},{"time":1699032549.069279,"value":28.505},{"time":1699032579.861928,"value":28.4811},{"time":1699032609.894083,"value":28.51},{"time":1699032641.179908,"value":28.5259},{"time":1699032671.876168,"value":28.535},{"time":1699032703.667801,"value":28.535},{"time":1699032734.588127,"value":28.53},{"time":1699032768.754475,"value":28.535},{"time":1699032799.186966,"value":28.545},{"time":1699032830.156905,"value":28.545},{"time":1699032862.27748,"value":28.5566},{"time":1699032893.11523,"value":28.565},{"time":1699032924.311403,"value":28.5602},{"time":1699032954.384318,"value":28.55},{"time":1699032986.072031,"value":28.56},{"time":1699033016.893769,"value":28.55},{"time":1699033052.66013,"value":28.545},{"time":1699033084.291394,"value":28.545},{"time":1699033114.515934,"value":28.5421},{"time":1699033145.197537,"value":28.545},{"time":1699033176.333519,"value":28.57},{"time":1699033206.63401,"value":28.56},{"time":1699033236.686076,"value":28.55},{"time":1699033268.171251,"value":28.5501},{"time":1699033298.418461,"value":28.56},{"time":1699033328.998694,"value":28.5499},{"time":1699033360.438221,"value":28.545},{"time":1699033391.117448,"value":28.55},{"time":1699033421.897801,"value":28.545},{"time":1699033454.051103,"value":28.545},{"time":1699033484.532679,"value":28.542},{"time":1699033516.350274,"value":28.545},{"time":1699033546.521525,"value":28.5492},{"time":1699033577.562039,"value":28.55},{"time":1699033607.737315,"value":28.555},{"time":1699033638.54558,"value":28.5678},{"time":1699033668.554351,"value":28.575},{"time":1699033699.302301,"value":28.5633},{"time":1699033730.460731,"value":28.565},{"time":1699033762.673027,"value":28.575},{"time":1699033794.148161,"value":28.575},{"time":1699033824.773173,"value":28.575},{"time":1699033854.849909,"value":28.585},{"time":1699033885.142025,"value":28.59},{"time":1699033921.045471,"value":28.585},{"time":1699033952.331513,"value":28.6},{"time":1699033982.83254,"value":28.5791},{"time":1699034013.501093,"value":28.58},{"time":1699034044.087637,"value":28.58},{"time":1699034078.677498,"value":28.575},{"time":1699034109.697928,"value":28.58},{"time":1699034141.55465,"value":28.585},{"time":1699034174.903248,"value":28.5975},{"time":1699034204.905456,"value":28.5988},{"time":1699034235.990239,"value":28.605},{"time":1699034266.372305,"value":28.62},{"time":1699034296.999459,"value":28.605},{"time":1699034327.163801,"value":28.6175},{"time":1699034357.599528,"value":28.61},{"time":1699034387.616422,"value":28.605},{"time":1699034418.346748,"value":28.615},{"time":1699034449.262557,"value":28.6163},{"time":1699034480.980216,"value":28.605},{"time":1699034511.700762,"value":28.615},{"time":1699034541.790667,"value":28.6081},{"time":1699034573.475685,"value":28.605},{"time":1699034603.900366,"value":28.605},{"time":1699034634.470582,"value":28.605},{"time":1699034665.088378,"value":28.605},{"time":1699034695.338479,"value":28.61},{"time":1699034727.668928,"value":28.6175},{"time":1699034759.330002,"value":28.615},{"time":1699034789.431709,"value":28.59},{"time":1699034819.464919,"value":28.57},{"time":1699034849.504792,"value":28.56},{"time":1699034880.634613,"value":28.545},{"time":1699034914.906698,"value":28.56},{"time":1699034946.525526,"value":28.575},{"time":1699034979.65996,"value":28.585},{"time":1699035009.83088,"value":28.585},{"time":1699035042.159775,"value":28.585},{"time":1699035072.263417,"value":28.59},{"time":1699035102.268049,"value":28.575},{"time":1699035134.984447,"value":28.5811},{"time":1699035165.124667,"value":28.545},{"time":1699035199.434971,"value":28.555},{"time":1699035231.165084,"value":28.565},{"time":1699035262.242613,"value":28.565},{"time":1699035295.228003,"value":28.5528},{"time":1699035325.812834,"value":28.5501},{"time":1699035359.048305,"value":28.54},{"time":1699035389.562774,"value":28.54},{"time":1699035419.568435,"value":28.535},{"time":1699035450.536662,"value":28.535},{"time":1699035481.812288,"value":28.535},{"time":1699035512.149093,"value":28.5329},{"time":1699035543.462682,"value":28.5303},{"time":1699035574.398519,"value":28.535},{"time":1699035604.533554,"value":28.545},{"time":1699035634.734566,"value":28.555},{"time":1699035665.129892,"value":28.5599},{"time":1699035698.020783,"value":28.55},{"time":1699035728.810689,"value":28.56},{"time":1699035759.208408,"value":28.545},{"time":1699035792.110835,"value":28.535},{"time":1699035822.833673,"value":28.5301},{"time":1699035853.453077,"value":28.54},{"time":1699035883.526002,"value":28.5286},{"time":1699035913.537874,"value":28.5202},{"time":1699035945.394027,"value":28.525},{"time":1699035977.757063,"value":28.53},{"time":1699036008.613806,"value":28.5203},{"time":1699036038.64446,"value":28.525},{"time":1699036069.245159,"value":28.535},{"time":1699036102.598537,"value":28.535},{"time":1699036134.079266,"value":28.54},{"time":1699036165.760358,"value":28.545},{"time":1699036196.196676,"value":28.535},{"time":1699036227.618383,"value":28.545},{"time":1699036257.918857,"value":28.545},{"time":1699036290.277819,"value":28.545},{"time":1699036331.975068,"value":28.56},{"time":1699036362.10073,"value":28.555},{"time":1699036392.152704,"value":28.565},{"time":1699036422.904539,"value":28.565},{"time":1699036453.098398,"value":28.575},{"time":1699036483.339595,"value":28.575},{"time":1699036515.753578,"value":28.5701},{"time":1699036552.597911,"value":28.575},{"time":1699036583.135742,"value":28.575},{"time":1699036615.344277,"value":28.58},{"time":1699036649.409152,"value":28.57},{"time":1699036679.824764,"value":28.57},{"time":1699036709.86081,"value":28.565},{"time":1699036740.120599,"value":28.57},{"time":1699036770.392548,"value":28.565},{"time":1699036800.486605,"value":28.5768},{"time":1699036831.729728,"value":28.575},{"time":1699036862.24438,"value":28.59},{"time":1699036892.517737,"value":28.57},{"time":1699036925.424913,"value":28.565},{"time":1699036955.898461,"value":28.57},{"time":1699036987.941833,"value":28.58},{"time":1699037018.421444,"value":28.58},{"time":1699037048.642141,"value":28.58},{"time":1699037079.20277,"value":28.575},{"time":1699037110.456448,"value":28.58},{"time":1699037149.692173,"value":28.575},{"time":1699037179.869723,"value":28.5699},{"time":1699037210.177633,"value":28.575},{"time":1699037240.952886,"value":28.57},{"time":1699037271.543123,"value":28.565},{"time":1699037302.371874,"value":28.565},{"time":1699037332.5291,"value":28.57},{"time":1699037363.232299,"value":28.575},{"time":1699037393.540813,"value":28.575},{"time":1699037426.488602,"value":28.575},{"time":1699037457.202756,"value":28.58},{"time":1699037490.960922,"value":28.585},{"time":1699037522.157414,"value":28.58},{"time":1699037552.463968,"value":28.59},{"time":1699037583.609046,"value":28.59},{"time":1699037614.263874,"value":28.575},{"time":1699037644.894987,"value":28.58},{"time":1699037675.108289,"value":28.58},{"time":1699037705.125869,"value":28.59},{"time":1699037736.407845,"value":28.59},{"time":1699037767.103615,"value":28.585},{"time":1699037797.955553,"value":28.585},{"time":1699037830.323193,"value":28.58},{"time":1699037861.914812,"value":28.59},{"time":1699037891.919869,"value":28.59},{"time":1699037921.958853,"value":28.585},{"time":1699037952.964111,"value":28.595},{"time":1699037984.792681,"value":28.595},{"time":1699038014.896635,"value":28.595},{"time":1699038048.026312,"value":28.595},{"time":1699038079.006321,"value":28.61},{"time":1699038109.560174,"value":28.5966},{"time":1699038140.403307,"value":28.5902},{"time":1699038170.622657,"value":28.595},{"time":1699038201.157202,"value":28.59},{"time":1699038232.079862,"value":28.595},{"time":1699038262.299407,"value":28.595},{"time":1699038292.751656,"value":28.595},{"time":1699038322.909942,"value":28.59},{"time":1699038361.448157,"value":28.58},{"time":1699038391.53072,"value":28.586},{"time":1699038422.809932,"value":28.585},{"time":1699038453.970595,"value":28.55},{"time":1699038487.556973,"value":28.55},{"time":1699038517.575851,"value":28.525},{"time":1699038547.7414,"value":28.53},{"time":1699038579.729595,"value":28.53},{"time":1699038612.61915,"value":28.53},{"time":1699038645.050427,"value":28.525},{"time":1699038675.706518,"value":28.515},{"time":1699038706.093003,"value":28.525},{"time":1699038736.140121,"value":28.49},{"time":1699038767.224909,"value":28.51},{"time":1699038797.866001,"value":28.52},{"time":1699038828.147159,"value":28.525},{"time":1699038858.149396,"value":28.53},{"time":1699038892.621333,"value":28.53},{"time":1699038923.833832,"value":28.5336},{"time":1699038954.372786,"value":28.525},{"time":1699038985.052729,"value":28.525},{"time":1699039016.260912,"value":28.525},{"time":1699039047.500972,"value":28.525},{"time":1699039079.490137,"value":28.5232},{"time":1699039110.546254,"value":28.525},{"time":1699039141.30159,"value":28.525},{"time":1699039174.438761,"value":28.515},{"time":1699039204.445207,"value":28.5099},{"time":1699039235.635853,"value":28.4953},{"time":1699039266.199143,"value":28.505},{"time":1699039299.155788,"value":28.5062},{"time":1699039329.700352,"value":28.515},{"time":1699039362.083176,"value":28.51},{"time":1699039398.665357,"value":28.505},{"time":1699039429.606721,"value":28.5144},{"time":1699039460.044345,"value":28.515},{"time":1699039491.337512,"value":28.5164},{"time":1699039521.756531,"value":28.5299},{"time":1699039552.309251,"value":28.515},{"time":1699039584.559271,"value":28.51},{"time":1699039617.034631,"value":28.515},{"time":1699039647.406552,"value":28.51},{"time":1699039677.525766,"value":28.5},{"time":1699039707.725356,"value":28.495},{"time":1699039738.204286,"value":28.485},{"time":1699039769.23429,"value":28.5},{"time":1699039799.757555,"value":28.5},{"time":1699039830.122355,"value":28.485},{"time":1699039860.599216,"value":28.495},{"time":1699039890.922102,"value":28.515},{"time":1699039921.006488,"value":28.5199},{"time":1699039951.052634,"value":28.51},{"time":1699039981.223662,"value":28.51},{"time":1699040011.874327,"value":28.515},{"time":1699040042.328555,"value":28.52},{"time":1699040073.565195,"value":28.515},{"time":1699040104.660631,"value":28.505},{"time":1699040135.58441,"value":28.495},{"time":1699040165.665001,"value":28.51},{"time":1699040195.75476,"value":28.5},{"time":1699040226.529336,"value":28.51},{"time":1699040257.412581,"value":28.5029},{"time":1699040287.571532,"value":28.52},{"time":1699040318.114847,"value":28.51},{"time":1699040348.224811,"value":28.525},{"time":1699040378.848563,"value":28.515},{"time":1699040409.315775,"value":28.52},{"time":1699040439.40886,"value":28.5},{"time":1699040471.794074,"value":28.49},{"time":1699040501.80612,"value":28.49},{"time":1699040536.469521,"value":28.49},{"time":1699040567.188042,"value":28.4823},{"time":1699040597.521292,"value":28.48},{"time":1699040627.634646,"value":28.46},{"time":1699040658.060501,"value":28.485},{"time":1699040688.31039,"value":28.48},{"time":1699040718.657189,"value":28.5},{"time":1699040749.244547,"value":28.495},{"time":1699040779.2772,"value":28.485},{"time":1699040809.289405,"value":28.4922},{"time":1699040840.252596,"value":28.485},{"time":1699040870.294358,"value":28.485},{"time":1699040902.092143,"value":28.475},{"time":1699040932.518442,"value":28.465},{"time":1699040962.781592,"value":28.4699},{"time":1699040994.042496,"value":28.46},{"time":1699041024.134933,"value":28.455},{"time":1699041054.542756,"value":28.45},{"time":1699041085.064692,"value":28.445},{"time":1699041115.464525,"value":28.455},{"time":1699041145.51358,"value":28.435},{"time":1699041175.622529,"value":28.43},{"time":1699041206.204989,"value":28.44},{"time":1699041236.379647,"value":28.41},{"time":1699041266.580813,"value":28.425},{"time":1699041296.703474,"value":28.425},{"time":1699041327.020957,"value":28.415},{"time":1699041357.276606,"value":28.415},{"time":1699041387.289358,"value":28.43},{"time":1699041417.974708,"value":28.415},{"time":1699041447.979434,"value":28.4242},{"time":1699041478.023777,"value":28.405},{"time":1699041508.292258,"value":28.4},{"time":1699041538.353258,"value":28.41},{"time":1699041568.360393,"value":28.425},{"time":1699041598.747699,"value":28.42}] + +f = json.loads(a) +print(f) \ No newline at end of file diff --git a/testy/fibonaccistoploss.py b/testy/fibonaccistoploss.py new file mode 100644 index 0000000..78c290f --- /dev/null +++ b/testy/fibonaccistoploss.py @@ -0,0 +1,179 @@ +import numpy as np +from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus +from typing import Tuple +from copy import deepcopy +from v2realbot.strategy.base import StrategyState +from v2realbot.strategyblocks.activetrade.helpers import get_max_profit_price, get_profit_target_price, get_override_for_active_trade, keyword_conditions_met +from v2realbot.utils.utils import safe_get +# FIBONACCI PRO PROFIT A SL + +##most used fibonacci retracement levels +# 23.6% retracement level = (stop loss price - current price) * 0.236 + current price +# 38.2% retracement level = (stop loss price - current price) * 0.382 + current price +# 50.0% retracement level = (stop loss price - current price) * 0.500 + current price +# 61.8% retracement level = (stop loss price - current price) * 0.618 + current price +# 78.6% retracement level = (stop loss price - current price) * 0.786 + current price + +#cil: moznost pouzit fibanocci scale pro castecny stoploss exit (percentage at each downlevel) +#a zároveň exit, případně add at each up level + +#up retracements (profit retracement) +# exit part of position at certain - +# [0.236, 0.382, 0.618, 1.0] - 25% off at each level? a nebo 5% add? - TBD vymyslet jak pojmout v direktive? +#down retracement (stoploss retracement) +# exit part of position at certain levels - TBD jak zapsat v dsirektive? +# [0.236, 0.382, 0.618, 1.0] - 25% off at each level + + + +# #tridu, kterou muze vyuzivat SL a Profit optimizer +class SLOptimizer: + """" + Class to handle SL positition optimization for active trade. It is assumed that two instances exists + one for LONG trade and one for SHORT. During evaluate call, settings is initialized from trade setting + and used for every call on that trade. When evaluate is called on different trade, it is again initialized + according to new trade settings. + + -samostatna instance pro short a long + -zatim pri opakovem prekroceni targetu nic nedelame (target aplikovany jen jednouo) + + exit_levels = aktuální levely, prekroceny je povazovan za vyuzitý a maze se + exit_sizes = aktualní size multipliers, prekroceny je povazovan za vyuzitý a maze se + init_exit_levels, init_exit_sizes - puvodni plne + """ + def __init__(self, direction: TradeDirection) -> None: + ##init - make exit size same length: + self.direction = direction + self.last_trade = 0 + + # def reset_levels(self): + # self.exit_levels = self.init_exit_levels + # self.exit_sizes = self.init_exit_sizes + + def get_trade_details(self, state: StrategyState): + trade: Trade = state.vars.activeTrade + #jde o novy trade - resetujeme levely + if trade.id != self.last_trade: + #inicializujeme a vymazeme pripadne puvodni + if self.initialize_levels(state) is False: + return None, None + self.last_trade = trade.id + #return cost_price, sl_price + return state.avgp, trade.stoploss_value + + def initialize_levels(self, state): + directive_name = 'SL_opt_exit_levels_'+str(self.direction) + SL_opt_exit_levels = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None)) + + directive_name = 'SL_opt_exit_sizes_'+str(self.direction) + SL_opt_exit_sizes = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None)) + + if SL_opt_exit_levels is None or SL_opt_exit_sizes is not None: + print("no directives found: SL_opt_exit_levels/SL_opt_exit_sizes") + return False + + if len(SL_opt_exit_sizes) == 1: + SL_opt_exit_sizes = SL_opt_exit_sizes * len(SL_opt_exit_levels) + + if len(SL_opt_exit_sizes) != len(SL_opt_exit_levels): + raise Exception("exit_sizes doesnt fit exit_levels") + self.init_exit_levels = deepcopy(SL_opt_exit_levels) + self.init_exit_sizes = deepcopy(SL_opt_exit_sizes) + self.exit_levels = SL_opt_exit_levels + self.exit_sizes = SL_opt_exit_sizes + print(f"new levels initialized {self.exit_levels=} {self.exit_sizes=}") + return True + + def get_initial_abs_levels(self, state): + """ + Returns price levels corresponding to initial setting of exit_levels + """ + cost_price, sl_price = self.get_trade_details(state) + if cost_price is None or sl_price is None: + return [] + curr_sl_distance = np.abs(cost_price - sl_price) + if self.direction == TradeDirection.SHORT : + return [cost_price + exit_level * curr_sl_distance for exit_level in self.init_exit_levels] + else: + return [cost_price - exit_level * curr_sl_distance for exit_level in self.init_exit_levels] + + def get_remaining_abs_levels(self, state): + """ + Returns price levels corresponding to remaing exit_levels for current trade + """ + cost_price, sl_price = self.get_trade_details(state) + if cost_price is None or sl_price is None: + return [] + curr_sl_distance = np.abs(cost_price - sl_price) + if self.direction == TradeDirection.SHORT : + return [cost_price + exit_level * curr_sl_distance for exit_level in self.exit_levels] + else: + return [cost_price - exit_level * curr_sl_distance for exit_level in self.exit_levels] + + def eval_position(self, state, data) -> Tuple[float, float]: + """Evaluates optimalization for current position and returns if the given level was + met and how to adjust exit position. + """ + cost_price, sl_price = self.get_trade_details(state) + if cost_price is None or sl_price is None: + print("no settings found") + return (None, None) + + current_price = data["close"] + # Calculate the distance of the cost prcie from the stop-loss value + curr_sl_distance = np.abs(cost_price - sl_price) + + level_met = None + exit_adjustment = None + + if len(self.exit_levels) == 0 or len(self.exit_sizes) == 0: + print("levels exhausted") + return (None, None) + + #for short + if self.direction == TradeDirection.SHORT : + #first available exit point + level_price = cost_price + self.exit_levels[0] * curr_sl_distance + if current_price > level_price: + # Remove the first element from exit_levels. + level_met = self.exit_levels.pop(0) + # Remove the first element from exit_sizes. + exit_adjustment = self.exit_sizes.pop(0) + #for shorts + else: + #price of first available exit point + level_price = cost_price - self.exit_levels[0] * curr_sl_distance + if current_price < level_price: + # Remove the first element from exit_levels. + level_met = self.exit_levels.pop(0) + # Remove the first element from exit_sizes. + exit_adjustment = self.exit_sizes.pop(0) + + return level_met, exit_adjustment + +#0.236, 0.382, 0.5, 0.618, 0.786, 1 +exit_levels_input = [0.236, 0.382, 0.618, 1] +exit_sizes_input = [0.5] #or [0.25, 0.25, 0.25] + +long_sl_optimizer = SLOptimizer(exit_levels_input, exit_sizes_input, TradeDirection.SHORT) +#short_sl_optimizer = SLOptimizer(exit_levels, exit_sizes, TradeDirection.SHORT) + +print(long_sl_optimizer.get_remaining_abs_levels(90,100)) + +#new LONG trade +level_met, exit_adjustment = long_sl_optimizer.eval_position(current_price=95,cost_price=90,sl_price=100) +print(long_sl_optimizer.get_remaining_abs_levels(90,100)) +print(level_met, exit_adjustment) +print(long_sl_optimizer.__dict__) + +level_met, exit_adjustment = long_sl_optimizer.eval_position(current_price=95,cost_price=90,sl_price=100) +print(long_sl_optimizer.get_remaining_abs_levels(90,100)) +print(level_met, exit_adjustment) +print(long_sl_optimizer.__dict__) + +level_met, exit_adjustment = long_sl_optimizer.eval_position(current_price=95,cost_price=90,sl_price=100) +print(long_sl_optimizer.get_remaining_abs_levels(90,100)) +print(level_met, exit_adjustment) +print(long_sl_optimizer.__dict__) + +#long_sl_optimizer.eval_position(100,120) diff --git a/v2realbot/ENTRY_ClassicSL_v01.py b/v2realbot/ENTRY_ClassicSL_v01.py index 6f744ad..c5278d3 100644 --- a/v2realbot/ENTRY_ClassicSL_v01.py +++ b/v2realbot/ENTRY_ClassicSL_v01.py @@ -161,7 +161,7 @@ def main(): cash=100000) #na sekundovem baru nezaokrouhlovat MAcko - s.add_data(symbol="BAC",rectype=RecordType.BAR,timeframe=2,minsize=100,update_ltp=True,align=StartBarAlign.ROUND,mintick=0, exthours=False) + s.add_data(symbol="BAC",rectype=RecordType.BAR,resolution=2,minsize=100,update_ltp=True,align=StartBarAlign.ROUND,mintick=0, exthours=False) #s.add_data(symbol="C",rectype=RecordType.BAR,timeframe=1,filters=None,update_ltp=True,align=StartBarAlign.ROUND,mintick=0) s.start() diff --git a/v2realbot/ENTRY_Vykladaci_RSI_MYSELL_type_NEW.py b/v2realbot/ENTRY_Vykladaci_RSI_MYSELL_type_NEW.py index 961e3a0..2e32dd3 100644 --- a/v2realbot/ENTRY_Vykladaci_RSI_MYSELL_type_NEW.py +++ b/v2realbot/ENTRY_Vykladaci_RSI_MYSELL_type_NEW.py @@ -1163,7 +1163,7 @@ def main(): cash=100000) #na sekundovem baru nezaokrouhlovat MAcko - s.add_data(symbol="BAC",rectype=RecordType.BAR,timeframe=2,minsize=100,update_ltp=True,align=StartBarAlign.ROUND,mintick=0, exthours=False) + s.add_data(symbol="BAC",rectype=RecordType.BAR,resolution=2,minsize=100,update_ltp=True,align=StartBarAlign.ROUND,mintick=0, exthours=False) #s.add_data(symbol="C",rectype=RecordType.BAR,timeframe=1,filters=None,update_ltp=True,align=StartBarAlign.ROUND,mintick=0) s.start() diff --git a/v2realbot/backtesting/backtester.py b/v2realbot/backtesting/backtester.py index 0fced61..20818ed 100644 --- a/v2realbot/backtesting/backtester.py +++ b/v2realbot/backtesting/backtester.py @@ -804,7 +804,7 @@ class Backtester: textik3 = html.Div(''' Stratvars:'''+ str(state.vars)) textik35 = html.Div(''' - Resolution:'''+ str(state.timeframe) + "s rectype:" + str(state.rectype)) + Resolution:'''+ str(state.resolution) + "s rectype:" + str(state.rectype)) textik4 = html.Div(''' Started at:''' + self.backtest_start.strftime("%d/%m/%Y, %H:%M:%S") + " Duration:"+str(self.backtest_end-self.backtest_start)) textik5 = html.Div(''' diff --git a/v2realbot/common/PrescribedTradeModel.py b/v2realbot/common/PrescribedTradeModel.py index 5f8a178..a15ebb8 100644 --- a/v2realbot/common/PrescribedTradeModel.py +++ b/v2realbot/common/PrescribedTradeModel.py @@ -24,6 +24,7 @@ class Trade(BaseModel): generated_by: Optional[str] = None direction: TradeDirection entry_price: Optional[float] = None + goal_price: Optional[float] = None size: Optional[int] = None # stoploss_type: TradeStoplossType stoploss_value: Optional[float] = None diff --git a/v2realbot/config.py b/v2realbot/config.py index 2f56645..bd2685a 100644 --- a/v2realbot/config.py +++ b/v2realbot/config.py @@ -4,6 +4,10 @@ from appdirs import user_data_dir #TODO vybrane dat do config db a managovat pres GUI +#AGGREGATOR filter trades +#NOTE pridana F - Inter Market Sweep Order - obcas vytvarela spajky +AGG_EXCLUDED_TRADES = ['C','O','4','B','7','V','P','W','U','Z','F'] + OFFLINE_MODE = False #ilog lvls = 0,1 - 0 debug, 1 info diff --git a/v2realbot/controller/services.py b/v2realbot/controller/services.py index fd51d28..1248c71 100644 --- a/v2realbot/controller/services.py +++ b/v2realbot/controller/services.py @@ -492,7 +492,6 @@ def batch_run_manager(id: UUID, runReq: RunRequest, rundays: list[RunDay]): #i.history += str(runner.__dict__)+"
" db.save() - #stratin run def run_stratin(id: UUID, runReq: RunRequest, synchronous: bool = False, inter_batch_params: dict = None): if runReq.mode == Mode.BT: @@ -756,7 +755,7 @@ def archive_runner(runner: Runner, strat: StrategyInstance, inter_batch_params: #get rid of attributes that are links to the models strat.state.vars["loaded_models"] = {} - settings = dict(resolution=strat.state.timeframe, + settings = dict(resolution=strat.state.resolution, rectype=strat.state.rectype, configs=dict( GROUP_TRADES_WITH_TIMESTAMP_LESS_THAN=GROUP_TRADES_WITH_TIMESTAMP_LESS_THAN, diff --git a/v2realbot/enums/enums.py b/v2realbot/enums/enums.py index 0d4cde5..1043ff5 100644 --- a/v2realbot/enums/enums.py +++ b/v2realbot/enums/enums.py @@ -55,6 +55,7 @@ class RecordType(str, Enum): BAR = "bar" CBAR = "cbar" + CBARVOLUME = "cbarvolume" TRADE = "trade" class Mode(str, Enum): diff --git a/v2realbot/loader/aggregator.py b/v2realbot/loader/aggregator.py index db4ccf1..ffb2f71 100644 --- a/v2realbot/loader/aggregator.py +++ b/v2realbot/loader/aggregator.py @@ -11,12 +11,12 @@ import threading from copy import deepcopy from msgpack import unpackb import os -from v2realbot.config import DATA_DIR, GROUP_TRADES_WITH_TIMESTAMP_LESS_THAN +from v2realbot.config import DATA_DIR, GROUP_TRADES_WITH_TIMESTAMP_LESS_THAN, AGG_EXCLUDED_TRADES class TradeAggregator: def __init__(self, rectype: RecordType = RecordType.BAR, - timeframe: int = 5, + resolution: int = 5, minsize: int = 100, update_ltp: bool = False, align: StartBarAlign = StartBarAlign.ROUND, @@ -27,22 +27,22 @@ class TradeAggregator: Create trade agregator. Instance accepts trades one by one and process them and returns output type Trade - return trade one by one (no change) - Bar - return finished bar in given timeframe + Bar - return finished bar in given resolution CBar - returns continuous bar, finished bar is marked by confirmed status Args: - timeframe (number): Resolution of bar in seconds + resolution (number): Resolution of bar in seconds update_ltp (bool): Whether to update global variable with price (usually only one instance does that) - align: Defines alignement of first bar. ROUND - according to timeframe( 5,10,15 - for 5s timeframe), RANDOM - according to timestamp of first trade + align: Defines alignement of first bar. ROUND - according to resolution( 5,10,15 - for 5s resolution), RANDOM - according to timestamp of first trade mintick: Applies for CBAR. Minimální mezera po potvrzeni baru a aktualizaci dalsiho nepotvrzeneho (např. pro 15s, muzeme chtit prvni tick po 5s). po teto dobe realtime. """ self.rectype: RecordType = rectype - self.timeframe = timeframe + self.resolution = resolution self.minsize = minsize self.update_ltp = update_ltp self.exthours = exthours - if mintick >= timeframe: - print("Mintick musi byt mensi nez timeframe") + if mintick >= resolution: + print("Mintick musi byt mensi nez resolution") raise Exception self.mintick = mintick @@ -51,7 +51,10 @@ class TradeAggregator: self.lasttimestamp = 0 #inicalizace pro prvni agregaci self.newBar = dict(high=0, low=999999, volume = 0, trades = 0, confirmed = 0, vwap = 0, close=0, index = 1, updated = 0) + self.openedVolumeBar = None + self.lastConfirmedTime = 0 self.bar_start = 0 + self.curr_bar_volume = None self.align = align self.tm: datetime = None self.firstpass = True @@ -87,7 +90,7 @@ class TradeAggregator: ## přidán W - average price trade, U - Extended hours - sold out of sequence, Z - Sold(Out of sequence) try: for i in data['c']: - if i in ('C','O','4','B','7','V','P','W','U','Z'): return [] + if i in AGG_EXCLUDED_TRADES: return [] except KeyError: pass @@ -151,8 +154,8 @@ class TradeAggregator: # if self.lasttimestamp ==0 and self.align: # if self.firstpass: # self.tm = datetime.fromtimestamp(data['t']) - # self.tm += timedelta(seconds=self.timeframe) - # self.tm = self.tm - timedelta(seconds=self.tm.second % self.timeframe,microseconds=self.tm.microsecond) + # self.tm += timedelta(seconds=self.resolution) + # self.tm = self.tm - timedelta(seconds=self.tm.second % self.resolution,microseconds=self.tm.microsecond) # self.firstpass = False # print("trade: ",datetime.fromtimestamp(data['t'])) # print("required",self.tm) @@ -160,10 +163,17 @@ class TradeAggregator: # return # else: pass + if self.rectype in (RecordType.BAR, RecordType.CBAR): + return await self.calculate_time_bar(data, symbol) + + if self.rectype == RecordType.CBARVOLUME: + return await self.calculate_volume_bar(data, symbol) + + async def calculate_time_bar(self, data, symbol): #print("barstart",datetime.fromtimestamp(self.bar_start)) #print("oriznute data z tradu", datetime.fromtimestamp(int(data['t']))) - #print("timeframe",self.timeframe) - if int(data['t']) - self.bar_start < self.timeframe: + #print("resolution",self.resolution) + if int(data['t']) - self.bar_start < self.resolution: issamebar = True else: issamebar = False @@ -202,10 +212,10 @@ class TradeAggregator: #MUSIME VRATIT ZPET - ten upraveny cas způsobuje spatne plneni v BT, kdyz tento bar triggeruje nakup # if self.align: # t = datetime.fromtimestamp(data['t']) - # t = t - timedelta(seconds=t.second % self.timeframe,microseconds=t.microsecond) + # t = t - timedelta(seconds=t.second % self.resolution,microseconds=t.microsecond) # #nebo pouzijeme datum tradu zaokrouhlene na vteriny (RANDOM) # else: - # #ulozime si jeho timestamp (odtum pocitame timeframe) + # #ulozime si jeho timestamp (odtum pocitame resolution) # t = datetime.fromtimestamp(int(data['t'])) # #self.newBar['updated'] = float(data['t']) - 0.001 @@ -276,18 +286,18 @@ class TradeAggregator: #zarovname time prvniho baru podle timeframu kam patří (např. 5, 10, 15 ...) (ROUND) if self.align == StartBarAlign.ROUND and self.bar_start == 0: t = datetime.fromtimestamp(data['t']) - t = t - timedelta(seconds=t.second % self.timeframe,microseconds=t.microsecond) + t = t - timedelta(seconds=t.second % self.resolution,microseconds=t.microsecond) self.bar_start = datetime.timestamp(t) #nebo pouzijeme datum tradu zaokrouhlene na vteriny (RANDOM) else: - #ulozime si jeho timestamp (odtum pocitame timeframe) + #ulozime si jeho timestamp (odtum pocitame resolution) t = datetime.fromtimestamp(int(data['t'])) #timestamp self.bar_start = int(data['t']) self.newBar['time'] = t - self.newBar['resolution'] = self.timeframe + self.newBar['resolution'] = self.resolution self.newBar['confirmed'] = 0 @@ -358,14 +368,178 @@ class TradeAggregator: else: return [] + async def calculate_volume_bar(self, data, symbol): + """" + Agreguje VOLUME BARS - + hlavni promenne + - self.openedVolumeBar (dict) = stavová obsahují aktivní nepotvrzený bar + - confirmedBars (list) = nestavová obsahuje confirmnute bary, které budou na konci funkceflushnuty + """"" + #volume_bucket = 10000 #daily MA volume z emackova na 30 deleno 50ti - dat do configu + volume_bucket = self.resolution + #potvrzene pripravene k vraceni + confirmedBars = [] + #potvrdi existujici a nastavi k vraceni + def confirm_existing(): + self.openedVolumeBar['confirmed'] = 1 + self.openedVolumeBar['vwap'] = self.vwaphelper / self.openedVolumeBar['volume'] + self.vwaphelper = 0 + + #ulozime zacatek potvrzeneho baru + self.lastBarConfirmed = self.openedVolumeBar['time'] + + self.openedVolumeBar['updated'] = data['t'] + confirmedBars.append(deepcopy(self.openedVolumeBar)) + self.openedVolumeBar = None + #TBD po každém potvrzení zvýšíme čas o nanosekundu (pro zobrazení v gui) + #data['t'] = data['t'] + 0.000001 + + #init unconfirmed - velikost bucketu kontrolovana predtim + def initialize_unconfirmed(size): + #inicializuji pro nový bar + self.vwaphelper += (data['p'] * size) + self.barindex +=1 + self.openedVolumeBar = { + "close": data['p'], + "high": data['p'], + "low": data['p'], + "open": data['p'], + "volume": size, + "trades": 1, + "hlcc4": data['p'], + "confirmed": 0, + "time": datetime.fromtimestamp(data['t']), + "updated": data['t'], + "vwap": data['p'], + "index": self.barindex, + "resolution":volume_bucket + } + + def update_unconfirmed(size): + #spočteme vwap - potřebujeme předchozí hodnoty + self.vwaphelper += (data['p'] * size) + self.openedVolumeBar['updated'] = data['t'] + self.openedVolumeBar['close'] = data['p'] + self.openedVolumeBar['high'] = max(self.openedVolumeBar['high'],data['p']) + self.openedVolumeBar['low'] = min(self.openedVolumeBar['low'],data['p']) + self.openedVolumeBar['volume'] = self.openedVolumeBar['volume'] + size + self.openedVolumeBar['trades'] = self.openedVolumeBar['trades'] + 1 + self.openedVolumeBar['vwap'] = self.vwaphelper / self.openedVolumeBar['volume'] + #pohrat si s timto round + self.openedVolumeBar['hlcc4'] = round((self.openedVolumeBar['high']+self.openedVolumeBar['low']+self.openedVolumeBar['close']+self.openedVolumeBar['close'])/4,3) + + #init new - confirmed + def initialize_confirmed(size): + #ulozime zacatek potvrzeneho baru + self.lastBarConfirmed = datetime.fromtimestamp(data['t']) + self.barindex +=1 + confirmedBars.append({ + "close": data['p'], + "high": data['p'], + "low": data['p'], + "open": data['p'], + "volume": size, + "trades": 1, + "hlcc4":data['p'], + "confirmed": 1, + "time": datetime.fromtimestamp(data['t']), + "updated": data['t'], + "vwap": data['p'], + "index": self.barindex, + "resolution":volume_bucket + }) + + #existuje stávající bar a vejdeme se do nej + if self.openedVolumeBar is not None and int(data['s']) + self.openedVolumeBar['volume'] < volume_bucket: + #vejdeme se do stávajícího baru (tzn. neprekracujeme bucket) + update_unconfirmed(int(data['s'])) + #updatujeme stávající nepotvrzeny bar + #nevejdem se do nej nebo neexistuje predchozi bar + else: + #1)existuje predchozi bar - doplnime zbytkem do valikosti bucketu a nastavime confirmed + if self.openedVolumeBar is not None: + + #doplnime je zbytkem + bucket_left = volume_bucket - self.openedVolumeBar['volume'] + # - update and confirm bar + update_unconfirmed(bucket_left) + confirm_existing() + + #zbytek mnozství jde do dalsiho zpracovani + data['s'] = int(data['s']) - bucket_left + #nastavime cas o nanosekundu vyssi + data['t'] = data['t'] + 0.000001 + + #2 vytvarime novy bar (bary) a vejdeme se do nej + if int(data['s']) < volume_bucket: + #vytvarime novy nepotvrzeny bar + initialize_unconfirmed(int(data['s'])) + #nevejdeme se do nej - pak vytvarime 1 až N dalsich baru (posledni nepotvrzený) + else: + # >>> for i in range(0, 550, 500): + # ... print(i) + # ... + # 0 + # 500 + + #vytvarime plne potvrzene buckety (kolik se jich plne vejde) + for size in range(0, int(data['s']), volume_bucket): + initialize_confirmed(volume_bucket) + #nastavime cas o nanosekundu vyssi + data['t'] = data['t'] + 0.000001 + #create complete full bucket with same prices and size + #naplnit do return pole + + #pokud je zbytek vytvorime z nej nepotvrzeny bar + zbytek = int(data['s']) % volume_bucket + + #ze zbytku vytvorime nepotvrzeny bar + if zbytek > 0: + initialize_unconfirmed(zbytek) + #create new open bar with size zbytek s otevrenym + + #je cena stejna od predchoziho tradu? pro nepotvrzeny cbar vracime jen pri zmene ceny + if self.last_price == data['p']: + self.diff_price = False + else: + self.diff_price = True + self.last_price = data['p'] + + if float(data['t']) - float(self.lasttimestamp) < GROUP_TRADES_WITH_TIMESTAMP_LESS_THAN: + self.trades_too_close = True + else: + self.trades_too_close = False + + #uložíme do předchozí hodnoty (poznáme tak open a close) + self.lasttimestamp = data['t'] + self.iterace += 1 + # print(self.iterace, data) + + #pokud mame confirm bary, tak FLUSHNEME confirm a i případný open (zrejme se pak nejaky vytvoril) + if len(confirmedBars) > 0: + return_set = confirmedBars + ([self.openedVolumeBar] if self.openedVolumeBar is not None else []) + confirmedBars = [] + return return_set + + #nemame confirm, FLUSHUJEME CBARVOLUME open - neresime zmenu ceny, ale neposilame kulomet (pokud nam nevytvari conf. bar) + if self.openedVolumeBar is not None and self.rectype == RecordType.CBARVOLUME: + + #zkousime pustit i stejnou cenu(potrebujeme kvuli MYSELLU), ale blokoval kulomet,tzn. trady mensi nez GROUP_TRADES_WITH_TIMESTAMP_LESS_THAN (1ms) + #if self.diff_price is True: + if self.trades_too_close is False: + return [self.openedVolumeBar] + else: + return [] + else: + return [] class TradeAggregator2Queue(TradeAggregator): """ Child of TradeAggregator - sends items to given queue In the future others will be added - TradeAggToTxT etc. """ - def __init__(self, symbol: str, queue: Queue, rectype: RecordType = RecordType.BAR, timeframe: int = 5, minsize: int = 100, update_ltp: bool = False, align: StartBarAlign = StartBarAlign.ROUND, mintick: int = 0, exthours: bool = False): - super().__init__(rectype=rectype, timeframe=timeframe, minsize=minsize, update_ltp=update_ltp, align=align, mintick=mintick, exthours=exthours) + def __init__(self, symbol: str, queue: Queue, rectype: RecordType = RecordType.BAR, resolution: int = 5, minsize: int = 100, update_ltp: bool = False, align: StartBarAlign = StartBarAlign.ROUND, mintick: int = 0, exthours: bool = False): + super().__init__(rectype=rectype, resolution=resolution, minsize=minsize, update_ltp=update_ltp, align=align, mintick=mintick, exthours=exthours) self.queue = queue self.symbol = symbol @@ -397,8 +571,8 @@ class TradeAggregator2List(TradeAggregator): """" stores records to the list """ - def __init__(self, symbol: str, btdata: list, rectype: RecordType = RecordType.BAR, timeframe: int = 5, minsize: int = 100, update_ltp: bool = False, align: StartBarAlign = StartBarAlign.ROUND, mintick: int = 0, exthours: bool = False): - super().__init__(rectype=rectype, timeframe=timeframe, minsize=minsize, update_ltp=update_ltp, align=align, mintick=mintick, exthours=exthours) + def __init__(self, symbol: str, btdata: list, rectype: RecordType = RecordType.BAR, resolution: int = 5, minsize: int = 100, update_ltp: bool = False, align: StartBarAlign = StartBarAlign.ROUND, mintick: int = 0, exthours: bool = False): + super().__init__(rectype=rectype, resolution=resolution, minsize=minsize, update_ltp=update_ltp, align=align, mintick=mintick, exthours=exthours) self.btdata = btdata self.symbol = symbol # self.debugfile = DATA_DIR + "/BACprices.txt" diff --git a/v2realbot/loader/cacher.py b/v2realbot/loader/cacher.py index edefb91..1b9159c 100644 --- a/v2realbot/loader/cacher.py +++ b/v2realbot/loader/cacher.py @@ -32,7 +32,7 @@ class Cacher: def __init__(self, rectype: RecordType = RecordType.BAR, - timeframe: int = 5, + resolution: int = 5, minsize: int = 100, update_ltp: bool = False, align: StartBarAlign = StartBarAlign.ROUND, diff --git a/v2realbot/static/js/archivechart.js b/v2realbot/static/js/archivechart.js index 3fd6cff..7a7c340 100644 --- a/v2realbot/static/js/archivechart.js +++ b/v2realbot/static/js/archivechart.js @@ -84,22 +84,37 @@ function transform_data(data) { }); } + + //pomocne + var last_time = 0 + var time = 0 + data.bars.time.forEach((element, index, array) => { sbars = {}; svolume = {}; svwap = {}; - sbars["time"] = element; + //tento algoritmus z duplicit dela posloupnosti a srovna i pripadne nekonzistence + //napr z .911 .911 .912 udela .911 .912 .913 + //TODO - možná dat do backendu agregatoru + if (last_time>=element) { + console.log("bars", "problem v case - zarovnano",time, last_time, element) + + data.bars.time[index] = data.bars.time[index-1] + 0.000001 + } + + last_time = data.bars.time[index] + sbars["time"] = data.bars.time[index]; sbars["close"] = data.bars.close[index] sbars["open"] = data.bars.open[index] sbars["high"] = data.bars.high[index] sbars["low"] = data.bars.low[index] - svwap["time"] = element + svwap["time"] = data.bars.time[index]; svwap["value"] = data.bars.vwap[index] - svolume["time"] = element + svolume["time"] = data.bars.time[index]; svolume["value"] = data.bars.volume[index] bars.push(sbars) @@ -107,6 +122,7 @@ function transform_data(data) { volume.push(svolume) }); transformed["bars"] = bars + //console.log(bars) transformed["vwap"] = vwap transformed["volume"] = volume var bars = [] @@ -585,7 +601,7 @@ function chart_indicators(data, visible, offset) { //DEBUG // if (key == 'tick_price') { - // console.log("problem tu",JSON.stringify(items)) + // console.log("problem tu",JSON.stringify(items,null,2)) // } //add data obj.series.setData(items) diff --git a/v2realbot/static/js/utils.js b/v2realbot/static/js/utils.js index 07ae829..97b7913 100644 --- a/v2realbot/static/js/utils.js +++ b/v2realbot/static/js/utils.js @@ -581,6 +581,21 @@ function toggleWide() { } } +//togle profit line +function toggleVolume() { + vis = true; + const elem = document.getElementById("volToggle"); + if (elem.classList.contains("switcher-active-item")) { + vis = false; + } + elem.classList.toggle("switcher-active-item"); + //v ifu kvuli workaroundu + if (volumeSeries) { + volumeSeries.applyOptions({ + visible: vis }); + } +} + //togle profit line function mrkLineToggle() { vis = true; @@ -613,15 +628,19 @@ function onItemClickedToggle(index) { elem.classList.toggle("switcher-active-item"); //v ifu kvuli workaroundu if (indList[index].series) { - indList[index].series.applyOptions({ - visible: vis }); + //console.log(indList[index].name, indList[index].series) + indList[index].series.applyOptions({ + visible: vis }); } //zatim takto workaround, pak vymyslet systemove pro vsechny tickbased indikatory - if (indList[index].name == "tick_price") { + tickIndicatorList = ["tick_price", "tick_volume"] + if (tickIndicatorList.includes(indList[index].name)) { if (!vis && indList[index].series) { + //console.log("pred", indList[index].name, indList[index].series) chart.removeSeries(indList[index].series) chart.timeScale().fitContent(); indList[index].series = null + //console.log("po", indList[index].name, indList[index].series) } } @@ -687,6 +706,18 @@ function populate_indicator_buttons(def) { }); buttonElement.appendChild(itemEl); + //button pro toggle fullscreenu + var itemEl = document.createElement('button'); + itemEl.innerText = "vol" + itemEl.classList.add('switcher-item'); + itemEl.classList.add('switcher-active-item'); + itemEl.style.color = "#99912b" + itemEl.id = "volToggle" + itemEl.addEventListener('click', function(e) { + toggleVolume(); + }); + buttonElement.appendChild(itemEl); + // //button pro toggle markeru nakupu/prodeju var itemEl = document.createElement('button'); itemEl.innerText = "mrk" diff --git a/v2realbot/strategy/StrategyClassicSL.py b/v2realbot/strategy/StrategyClassicSL.py index fcd4c89..dbf0aad 100644 --- a/v2realbot/strategy/StrategyClassicSL.py +++ b/v2realbot/strategy/StrategyClassicSL.py @@ -16,6 +16,7 @@ import numpy as np from threading import Event from uuid import UUID, uuid4 from v2realbot.strategyblocks.indicators.indicators_hub import populate_all_indicators +from v2realbot.strategyblocks.activetrade.helpers import get_profit_target_price class StrategyClassicSL(Strategy): """ @@ -69,7 +70,6 @@ class StrategyClassicSL(Strategy): return False - async def add_followup(self, direction: TradeDirection, size: int, signal_name: str): trade_to_add = Trade( id=uuid4(), @@ -130,7 +130,10 @@ class StrategyClassicSL(Strategy): #pokud jde o finalni FILL - pridame do pole tento celkovy relativnich profit (ze ktereho se pocita kumulativni relativni profit) rel_profit_cum_calculated = 0 + if data.event == TradeEvent.FILL: + #TODO pokud mame partial exit, tak se spravne vypocita relativni profit, ale + # je jen na mensi mnozszvi take z nej delat cum_calculate je blbost - OPRAVIT self.state.rel_profit_cum.append(rel_profit) rel_profit_cum_calculated = round(np.mean(self.state.rel_profit_cum),5) @@ -193,6 +196,11 @@ class StrategyClassicSL(Strategy): #zapisujeme last entry price self.state.last_entry_price["long"] = data.price + #pokud neni nastaveno goal_price tak vyplnujeme defaultem + if self.state.vars.activeTrade.goal_price is None: + dat = dict(close=data.price) + self.state.vars.activeTrade.goal_price = get_profit_target_price(self.state, dat, TradeDirection.LONG) + #ic("vstupujeme do orderupdatebuy") print(data) #dostavame zde i celkové akutální množství - ukládáme @@ -308,6 +316,13 @@ class StrategyClassicSL(Strategy): if data.event == TradeEvent.FILL: #zapisujeme last entry price self.state.last_entry_price["short"] = data.price + #pokud neni nastaveno goal_price tak vyplnujeme defaultem + if self.state.vars.activeTrade.goal_price is None: + dat = dict(close=data.price) + self.state.vars.activeTrade.goal_price = get_profit_target_price(self.state, dat, TradeDirection.SHORT) + #sem v budoucnu dat i update SL + #if self.state.vars.activeTrade.stoploss_value is None: + #update pozic, v trade update je i pocet zbylych pozic old_avgp = self.state.avgp diff --git a/v2realbot/strategy/base.py b/v2realbot/strategy/base.py index 9a09329..bd7806e 100644 --- a/v2realbot/strategy/base.py +++ b/v2realbot/strategy/base.py @@ -16,6 +16,7 @@ from v2realbot.loader.trade_ws_streamer import Trade_WS_Streamer from v2realbot.interfaces.general_interface import GeneralInterface from v2realbot.interfaces.backtest_interface import BacktestInterface from v2realbot.interfaces.live_interface import LiveInterface +import v2realbot.common.PrescribedTradeModel as ptm from alpaca.trading.enums import OrderSide from v2realbot.backtesting.backtester import Backtester #from alpaca.trading.models import TradeUpdate @@ -26,6 +27,7 @@ import json from uuid import UUID from rich import print as printnow from collections import defaultdict +import v2realbot.strategyblocks.activetrade.sl.optimsl as optimsl if PROFILING_NEXT_ENABLED: from pyinstrument import Profiler @@ -81,7 +83,7 @@ class Strategy: def add_data(self, symbol: str, rectype: RecordType = RecordType.BAR, - timeframe: int = 5, + resolution: int = 5, minsize: int = 100, update_ltp: bool = False, align: StartBarAlign = StartBarAlign.ROUND, @@ -93,8 +95,8 @@ class Strategy: ##stejne tak podporit i ruzne resolutions, zatim take natvrdo prvni self.rectype = rectype self.state.rectype = rectype - self.state.timeframe = timeframe - stream = TradeAggregator2Queue(symbol=symbol,queue=self.q1,rectype=rectype,timeframe=timeframe,update_ltp=update_ltp,align=align,mintick = mintick, exthours=exthours, minsize=minsize) + self.state.resolution = resolution + stream = TradeAggregator2Queue(symbol=symbol,queue=self.q1,rectype=rectype,resolution=resolution,update_ltp=update_ltp,align=align,mintick = mintick, exthours=exthours, minsize=minsize) self._streams.append(stream) self.dataloader.add_stream(stream) @@ -168,7 +170,7 @@ class Strategy: #implementovat az podle skutecnych pozadavku #self.state.indicators['time'].append(datetime.fromtimestamp(self.state.last_trade_time)) #self.append_trade(self.state.trades,item) - elif self.rectype == RecordType.CBAR: + elif self.rectype in (RecordType.CBAR, RecordType.CBARVOLUME): if self.nextnew: #standardni identifikatory - populace hist zaznamu pouze v novem baru (dale se deji jen udpaty) for key in self.state.indicators: @@ -216,14 +218,14 @@ class Strategy: #pokud jsou nastaveny secondary - zatím skrz stratvars - pripadne do do API #zatim jedno, predelat pak na list - # if safe_get(self.state.vars, "secondary_timeframe",None): + # if safe_get(self.state.vars, "secondary_resolution",None): # self.process_secondary_indicators(item) # #tady jsem skoncil # def process_secondary_indicators(self, item): # #toto je voláno každý potvrzený CBAR - # resolution = int(safe_get(self.state.vars, "secondary_timeframe",10)) + # resolution = int(safe_get(self.state.vars, "secondary_resolution",10)) # if int(item['resolution']) >= int(resolution) or int(resolution) % int(item['resolution']) != 0: # self.state.ilog(e=f"Secondary res {resolution} must be higher than main resolution {item['resolution']} a jejim delitelem") @@ -273,14 +275,14 @@ class Strategy: a,p = self.interface.pos() if a != -1: self.state.avgp, self.state.positions = a,p - elif self.rectype == RecordType.CBAR and item['confirmed'] == 1: + elif self.rectype in (RecordType.CBAR, RecordType.CBARVOLUME) and item['confirmed'] == 1: a,p = self.interface.pos() if a != -1: self.state.avgp, self.state.positions = a,p """update state.last_trade_time a time of iteration""" def update_times(self, item): - if self.rectype == RecordType.BAR or self.rectype == RecordType.CBAR: + if self.rectype == RecordType.BAR or self.rectype in (RecordType.CBAR, RecordType.CBARVOLUME): self.state.last_trade_time = item['updated'] elif self.rectype == RecordType.TRADE: self.state.last_trade_time = item['t'] @@ -522,7 +524,7 @@ class Strategy: if self.rtqueue is not None: rt_out = dict() - if self.rectype == RecordType.BAR or self.rectype == RecordType.CBAR: + if self.rectype == RecordType.BAR or self.rectype in (RecordType.CBAR, RecordType.CBARVOLUME): rt_out["bars"] = item else: rt_out["trades"] = item @@ -665,10 +667,12 @@ class StrategyState: #time of last trade processed self.last_trade_time = 0 self.last_entry_price=dict(long=0,short=999) - self.timeframe = None + self.resolution = None self.runner_id = runner_id self.bt = bt self.ilog_save = ilog_save + self.sl_optimizer_short = optimsl.SLOptimizer(ptm.TradeDirection.SHORT) + self.sl_optimizer_long = optimsl.SLOptimizer(ptm.TradeDirection.LONG) bars = {'high': [], 'low': [], @@ -698,7 +702,7 @@ class StrategyState: #pro mapping indikatoru pro pouziti v operation expressionu self.ind_mapping = {} self.cbar_indicators = AttributeDict(time=[]) - #secondary timeframe indicators + #secondary resolution indicators #self.secondary_indicators = AttributeDict(time=[], sec_price=[]) self.statinds = AttributeDict() #these methods can be overrided by StrategyType (to add or alter its functionality) diff --git a/v2realbot/strategyblocks/activetrade/close/close_position.py b/v2realbot/strategyblocks/activetrade/close/close_position.py index 438bc1b..3a89ade 100644 --- a/v2realbot/strategyblocks/activetrade/close/close_position.py +++ b/v2realbot/strategyblocks/activetrade/close/close_position.py @@ -43,3 +43,27 @@ def close_position(state, data, direction: TradeDirection, reason: str, followup state.vars.last_exit_index = data["index"] if followup is not None: state.vars.requested_followup = followup + +#close only partial position - no followup here, size multiplier must be between 0 and 1 +def close_position_partial(state, data, direction: TradeDirection, reason: str, size: float): + if size <= 0 or size >=1: + raise Exception(f"size must be betweem 0 and 1") + size_abs = abs(int(int(state.positions)*size)) + state.ilog(lvl=1,e=f"CLOSING TRADE PART: {size_abs} {size} {reason} {str(direction)}", curr_price=data["close"], trade=state.vars.activeTrade) + if direction == TradeDirection.SHORT: + res = state.buy(size=size_abs) + if isinstance(res, int) and res < 0: + raise Exception(f"error in required operation STOPLOSS PARTIAL BUY {reason} {res}") + + elif direction == TradeDirection.LONG: + res = state.sell(size=size_abs) + if isinstance(res, int) and res < 0: + raise Exception(f"error in required operation STOPLOSS PARTIAL SELL {res}") + else: + raise Exception(f"unknow TradeDirection in close_position") + + #pri uzavreni tradu zapisujeme SL history - lepsi zorbazeni v grafu + insert_SL_history(state) + state.vars.pending = state.vars.activeTrade.id + #state.vars.activeTrade = None + #state.vars.last_exit_index = data["index"] \ No newline at end of file diff --git a/v2realbot/strategyblocks/activetrade/close/evaluate_close.py b/v2realbot/strategyblocks/activetrade/close/evaluate_close.py index 93f360a..40a77db 100644 --- a/v2realbot/strategyblocks/activetrade/close/evaluate_close.py +++ b/v2realbot/strategyblocks/activetrade/close/evaluate_close.py @@ -1,4 +1,4 @@ -from v2realbot.strategyblocks.activetrade.close.close_position import close_position +from v2realbot.strategyblocks.activetrade.close.close_position import close_position, close_position_partial from v2realbot.strategy.base import StrategyState from v2realbot.enums.enums import Followup from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus @@ -12,6 +12,7 @@ from traceback import format_exc from v2realbot.strategyblocks.activetrade.close.eod_exit import eod_exit_activated from v2realbot.strategyblocks.activetrade.close.conditions import dontexit_protection_met, exit_conditions_met from v2realbot.strategyblocks.activetrade.helpers import get_max_profit_price, get_profit_target_price, get_override_for_active_trade, keyword_conditions_met +from v2realbot.strategyblocks.activetrade.sl.optimsl import SLOptimizer def eval_close_position(state: StrategyState, data): curr_price = float(data['close']) @@ -25,11 +26,26 @@ def eval_close_position(state: StrategyState, data): #mame short pozice - (IDEA: rozlisovat na zaklade aktivniho tradu - umozni mi spoustet i pri soucasne long pozicemi) if int(state.positions) < 0: #get TARGET PRICE pro dany smer a signal - goal_price = get_profit_target_price(state, data, TradeDirection.SHORT) + + #pokud existujeme bereme z nastaveni tradu a nebo z defaultu + if state.vars.activeTrade.goal_price is not None: + goal_price = state.vars.activeTrade.goal_price + else: + goal_price = get_profit_target_price(state, data, TradeDirection.SHORT) + max_price = get_max_profit_price(state, data, TradeDirection.SHORT) - state.ilog(lvl=1,e=f"Goal price {str(TradeDirection.SHORT)} {goal_price} max price {max_price}") + state.ilog(lvl=1,e=f"Def Goal price {str(TradeDirection.SHORT)} {goal_price} max price {max_price}") - #SL - execution + #SL OPTIMALIZATION - PARTIAL EXIT + level_met, exit_adjustment = state.sl_optimizer_short.eval_position(state, data) + if level_met is not None and exit_adjustment is not None: + position = state.positions * exit_adjustment + state.ilog(lvl=1,e=f"SL OPTIMIZATION ENGAGED {str(TradeDirection.SHORT)} {position=} {level_met=} {exit_adjustment}", initial_levels=str(state.sl_optimizer_short.get_initial_abs_levels(state)), rem_levels=str(state.sl_optimizer_short.get_remaining_abs_levels(state)), exit_levels=str(state.sl_optimizer_short.exit_levels), exit_sizes=str(state.sl_optimizer_short.exit_sizes)) + printanyway(f"SL OPTIMIZATION ENGAGED {str(TradeDirection.SHORT)} {position=} {level_met=} {exit_adjustment}") + close_position_partial(state=state, data=data, direction=TradeDirection.SHORT, reason=F"SL OPT LEVEL {level_met} REACHED", size=exit_adjustment) + return + + #FULL SL reached - execution if curr_price > state.vars.activeTrade.stoploss_value: directive_name = 'reverse_for_SL_exit_short' @@ -90,13 +106,25 @@ def eval_close_position(state: StrategyState, data): elif int(state.positions) > 0: #get TARGET PRICE pro dany smer a signal - goal_price = get_profit_target_price(state, data, TradeDirection.LONG) + #pokud existujeme bereme z nastaveni tradu a nebo z defaultu + if state.vars.activeTrade.goal_price is not None: + goal_price = state.vars.activeTrade.goal_price + else: + goal_price = get_profit_target_price(state, data, TradeDirection.LONG) + max_price = get_max_profit_price(state, data, TradeDirection.LONG) state.ilog(lvl=1,e=f"Goal price {str(TradeDirection.LONG)} {goal_price} max price {max_price}") - #EOD EXIT - TBD + #SL OPTIMALIZATION - PARTIAL EXIT + level_met, exit_adjustment = state.sl_optimizer_long.eval_position(state, data) + if level_met is not None and exit_adjustment is not None: + position = state.positions * exit_adjustment + state.ilog(lvl=1,e=f"SL OPTIMIZATION ENGAGED {str(TradeDirection.LONG)} {position=} {level_met=} {exit_adjustment}", initial_levels=str(state.sl_optimizer_long.get_initial_abs_levels(state)), rem_levels=str(state.sl_optimizer_long.get_remaining_abs_levels(state)), exit_levels=str(state.sl_optimizer_long.exit_levels), exit_sizes=str(state.sl_optimizer_long.exit_sizes)) + printanyway(f"SL OPTIMIZATION ENGAGED {str(TradeDirection.LONG)} {position=} {level_met=} {exit_adjustment}") + close_position_partial(state=state, data=data, direction=TradeDirection.LONG, reason=f"SL OPT LEVEL {level_met} REACHED", size=exit_adjustment) + return - #SL - execution + #SL FULL execution if curr_price < state.vars.activeTrade.stoploss_value: directive_name = 'reverse_for_SL_exit_long' reverse_for_SL_exit = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, "no")) diff --git a/v2realbot/strategyblocks/activetrade/helpers.py b/v2realbot/strategyblocks/activetrade/helpers.py index 48b5570..2cb54cf 100644 --- a/v2realbot/strategyblocks/activetrade/helpers.py +++ b/v2realbot/strategyblocks/activetrade/helpers.py @@ -1,5 +1,3 @@ -from v2realbot.strategy.base import StrategyState -from v2realbot.strategy.StrategyOrderLimitVykladaciNormalizedMYSELL import StrategyOrderLimitVykladaciNormalizedMYSELL from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, Followup from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, print, safe_get, is_still, is_window_open, eval_cond_dict, crossed_down, crossed_up, crossed, is_pivot, json_serial, pct_diff, create_new_bars, slice_dict_lists @@ -161,12 +159,25 @@ def get_profit_target_price(state, data, direction: TradeDirection): directive_name = 'profit_'+str(smer) def_profit = get_override_for_active_trade(state, directive_name=directive_name, default_value=def_profit_both_directions) - normalized_def_profit = normalize_tick(state, data, float(def_profit)) + #mame v direktivve ticky + if isinstance(def_profit, (float, int)): + normalized_def_profit = normalize_tick(state, data, float(def_profit)) - state.ilog(lvl=0,e=f"PROFIT {def_profit=} {normalized_def_profit=}") + state.ilog(lvl=0,e=f"PROFIT {def_profit=} {normalized_def_profit=}") + + base_price = state.avgp if state.avgp != 0 else data["close"] + + to_return = price2dec(float(base_price)+normalized_def_profit,3) if direction == TradeDirection.LONG else price2dec(float(base_price)-normalized_def_profit,3) + #mame v direktive indikator + elif isinstance(def_profit, str): + to_return = float(value_or_indicator(state, def_profit)) + + if direction == TradeDirection.LONG and to_return < data['close'] or direction == TradeDirection.SHORT and to_return > data['close']: + state.ilog(lvl=1,e=f"SPATNA HODOTA DOTAZENEHO PROFITU z ind {def_profit} {to_return=} {smer} {data['close']}") + raise Exception(f"SPATNA HODOTA DOTAZENEHO PROFITU z ind{def_profit} {to_return=} {smer} {data['close']}") + state.ilog(lvl=1,e=f"DOTAZENY PROFIT z indikatoru {def_profit} {to_return=}") + return to_return - return price2dec(float(state.avgp)+normalized_def_profit,3) if int(state.positions) > 0 else price2dec(float(state.avgp)-normalized_def_profit,3) - def get_max_profit_price(state, data, direction: TradeDirection): if direction == TradeDirection.LONG: smer = "long" diff --git a/v2realbot/strategyblocks/activetrade/sl/optimsl.py b/v2realbot/strategyblocks/activetrade/sl/optimsl.py new file mode 100644 index 0000000..1d550da --- /dev/null +++ b/v2realbot/strategyblocks/activetrade/sl/optimsl.py @@ -0,0 +1,151 @@ +import numpy as np +from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus +from typing import Tuple +from copy import deepcopy +from v2realbot.strategyblocks.activetrade.helpers import get_override_for_active_trade +from v2realbot.utils.utils import safe_get +# FIBONACCI PRO PROFIT A SL + +##most used fibonacci retracement levels +# 23.6% retracement level = (stop loss price - current price) * 0.236 + current price +# 38.2% retracement level = (stop loss price - current price) * 0.382 + current price +# 50.0% retracement level = (stop loss price - current price) * 0.500 + current price +# 61.8% retracement level = (stop loss price - current price) * 0.618 + current price +# 78.6% retracement level = (stop loss price - current price) * 0.786 + current price + +#cil: moznost pouzit fibanocci scale pro castecny stoploss exit (percentage at each downlevel) +#a zároveň exit, případně add at each up level + +#up retracements (profit retracement) +# exit part of position at certain - +# [0.236, 0.382, 0.618, 1.0] - 25% off at each level? a nebo 5% add? - TBD vymyslet jak pojmout v direktive? +#down retracement (stoploss retracement) +# exit part of position at certain levels - TBD jak zapsat v dsirektive? +# [0.236, 0.382, 0.618, 1.0] - 25% off at each level + + + +# #tridu, kterou muze vyuzivat SL a Profit optimizer +class SLOptimizer: + """" + Class to handle SL positition optimization for active trade. It is assumed that two instances exists + one for LONG trade and one for SHORT. During evaluate call, settings is initialized from trade setting + and used for every call on that trade. When evaluate is called on different trade, it is again initialized + according to new trade settings. + + -samostatna instance pro short a long + -zatim pri opakovem prekroceni targetu nic nedelame (target aplikovany jen jednouo) + + exit_levels = aktuální levely, prekroceny je povazovan za vyuzitý a maze se + exit_sizes = aktualní size multipliers, prekroceny je povazovan za vyuzitý a maze se + init_exit_levels, init_exit_sizes - puvodni plne + """ + def __init__(self, direction: TradeDirection) -> None: + ##init - make exit size same length: + self.direction = direction + self.last_trade = 0 + + # def reset_levels(self): + # self.exit_levels = self.init_exit_levels + # self.exit_sizes = self.init_exit_sizes + + def get_trade_details(self, state): + trade: Trade = state.vars.activeTrade + #jde o novy trade - resetujeme levely + if trade.id != self.last_trade: + #inicializujeme a vymazeme pripadne puvodni + if self.initialize_levels(state) is False: + return None, None + self.last_trade = trade.id + #return cost_price, sl_price + return state.avgp, trade.stoploss_value + + def initialize_levels(self, state): + directive_name = 'SL_opt_exit_levels_'+str(self.direction.value) + SL_opt_exit_levels = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None)) + + directive_name = 'SL_opt_exit_sizes_'+str(self.direction.value) + SL_opt_exit_sizes = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None)) + + if SL_opt_exit_levels is None or SL_opt_exit_sizes is None: + #print("no directives found: SL_opt_exit_levels/SL_opt_exit_sizes") + return False + + if len(SL_opt_exit_sizes) == 1: + SL_opt_exit_sizes = SL_opt_exit_sizes * len(SL_opt_exit_levels) + + if len(SL_opt_exit_sizes) != len(SL_opt_exit_levels): + raise Exception("exit_sizes doesnt fit exit_levels") + self.init_exit_levels = deepcopy(SL_opt_exit_levels) + self.init_exit_sizes = deepcopy(SL_opt_exit_sizes) + self.exit_levels = SL_opt_exit_levels + self.exit_sizes = SL_opt_exit_sizes + print(f"new levels initialized {self.exit_levels=} {self.exit_sizes=}") + return True + + def get_initial_abs_levels(self, state): + """ + Returns price levels corresponding to initial setting of exit_levels + """ + cost_price, sl_price = self.get_trade_details(state) + if cost_price is None or sl_price is None: + return [] + curr_sl_distance = np.abs(cost_price - sl_price) + if self.direction == TradeDirection.SHORT : + return [cost_price + exit_level * curr_sl_distance for exit_level in self.init_exit_levels] + else: + return [cost_price - exit_level * curr_sl_distance for exit_level in self.init_exit_levels] + + def get_remaining_abs_levels(self, state): + """ + Returns price levels corresponding to remaing exit_levels for current trade + """ + cost_price, sl_price = self.get_trade_details(state) + if cost_price is None or sl_price is None: + return [] + curr_sl_distance = np.abs(cost_price - sl_price) + if self.direction == TradeDirection.SHORT : + return [cost_price + exit_level * curr_sl_distance for exit_level in self.exit_levels] + else: + return [cost_price - exit_level * curr_sl_distance for exit_level in self.exit_levels] + + def eval_position(self, state, data) -> Tuple[float, float]: + """Evaluates optimalization for current position and returns if the given level was + met and how to adjust exit position. + """ + cost_price, sl_price = self.get_trade_details(state) + if cost_price is None or sl_price is None: + #print("no settings found") + return (None, None) + + current_price = data["close"] + # Calculate the distance of the cost prcie from the stop-loss value + curr_sl_distance = np.abs(cost_price - sl_price) + + level_met = None + exit_adjustment = None + + if len(self.exit_levels) == 0 or len(self.exit_sizes) == 0: + #print("levels exhausted") + return (None, None) + + #for short + if self.direction == TradeDirection.SHORT : + #first available exit point + level_price = cost_price + self.exit_levels[0] * curr_sl_distance + if current_price > level_price: + # Remove the first element from exit_levels. + level_met = self.exit_levels.pop(0) + # Remove the first element from exit_sizes. + exit_adjustment = self.exit_sizes.pop(0) + #for shorts + else: + #price of first available exit point + level_price = cost_price - self.exit_levels[0] * curr_sl_distance + if current_price < level_price: + # Remove the first element from exit_levels. + level_met = self.exit_levels.pop(0) + # Remove the first element from exit_sizes. + exit_adjustment = self.exit_sizes.pop(0) + + return level_met, exit_adjustment diff --git a/v2realbot/strategyblocks/helpers.py b/v2realbot/strategyblocks/helpers.py index df9b729..de1e215 100644 --- a/v2realbot/strategyblocks/helpers.py +++ b/v2realbot/strategyblocks/helpers.py @@ -1,7 +1,3 @@ -from v2realbot.strategy.base import StrategyState -from v2realbot.strategy.StrategyOrderLimitVykladaciNormalizedMYSELL import StrategyOrderLimitVykladaciNormalizedMYSELL -from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, Followup -from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, print, safe_get, is_still, is_window_open, eval_cond_dict, crossed_down, crossed_up, crossed, is_pivot, json_serial, pct_diff, create_new_bars, slice_dict_lists from v2realbot.utils.directive_utils import get_conditions_from_configuration from v2realbot.ml.mlutils import load_model diff --git a/v2realbot/strategyblocks/indicators/cbar_price.py b/v2realbot/strategyblocks/indicators/cbar_price.py index 314b5cd..1edf005 100644 --- a/v2realbot/strategyblocks/indicators/cbar_price.py +++ b/v2realbot/strategyblocks/indicators/cbar_price.py @@ -1,23 +1,57 @@ from v2realbot.strategy.base import StrategyState +from v2realbot.enums.enums import RecordType def populate_cbar_tick_price_indicator(data, state: StrategyState): - try: - #pokud v potvrzovacím baru nebyly zmeny, nechavam puvodni hodnoty - # if tick_delta_volume == 0: - # state.indicators.tick_price[-1] = state.indicators.tick_price[-2] - # state.indicators.tick_volume[-1] = state.indicators.tick_volume[-2] - # else: + conf_bar = data['confirmed'] - #tick_price = round2five(data['close']) - tick_price = data['close'] - tick_delta_volume = data['volume'] - state.vars.last_tick_volume + #specifická sekce pro CBARVOLUME, kde vzdy máme nova data v confirmation baru (tzn. tickprice pocitame jak pri potvrzenem tak nepotvrzenem) + if state.rectype == RecordType.CBARVOLUME: + try: + tick_price = data['close'] + tick_delta_volume = data['volume'] - state.vars.last_tick_volume - state.cbar_indicators.tick_price[-1] = tick_price - state.cbar_indicators.tick_volume[-1] = tick_delta_volume - except: - pass + state.cbar_indicators.tick_price[-1] = tick_price + state.cbar_indicators.tick_volume[-1] = tick_delta_volume + except: + pass - state.ilog(lvl=0,e=f"TICK PRICE {tick_price} VOLUME {tick_delta_volume} {data['confirmed']=}", prev_price=state.vars.last_tick_price, prev_volume=state.vars.last_tick_volume) + state.ilog(lvl=0,e=f"TICK PRICE CBARV {tick_price} VOLUME {tick_delta_volume} {data['confirmed']=}", prev_price=state.vars.last_tick_price, prev_volume=state.vars.last_tick_volume) - state.vars.last_tick_price = tick_price - state.vars.last_tick_volume = data['volume'] \ No newline at end of file + state.vars.last_tick_price = tick_price + state.vars.last_tick_volume = data['volume'] + + if conf_bar == 1: + #pri potvrzem CBARu nulujeme counter volume pro tick based indicator + state.vars.last_tick_volume = 0 + state.vars.next_new = 1 + + #pro standardní CBARy + else: + if conf_bar == 1: + #pri potvrzem CBARu nulujeme counter volume pro tick based indicator + state.vars.last_tick_volume = 0 + state.vars.next_new = 1 + + + #naopak pri CBARu confirmation bar nema zadna nova data (tzn. tickprice pocitame jen pri potvrzenem) + else: + try: + #pokud v potvrzovacím baru nebyly zmeny, nechavam puvodni hodnoty + # if tick_delta_volume == 0: + # state.indicators.tick_price[-1] = state.indicators.tick_price[-2] + # state.indicators.tick_volume[-1] = state.indicators.tick_volume[-2] + # else: + + #tick_price = round2five(data['close']) + tick_price = data['close'] + tick_delta_volume = data['volume'] - state.vars.last_tick_volume + + state.cbar_indicators.tick_price[-1] = tick_price + state.cbar_indicators.tick_volume[-1] = tick_delta_volume + except: + pass + + state.ilog(lvl=0,e=f"TICK PRICE CBAR {tick_price} VOLUME {tick_delta_volume} {data['confirmed']=}", prev_price=state.vars.last_tick_price, prev_volume=state.vars.last_tick_volume) + + state.vars.last_tick_price = tick_price + state.vars.last_tick_volume = data['volume'] \ No newline at end of file diff --git a/v2realbot/strategyblocks/indicators/helpers.py b/v2realbot/strategyblocks/indicators/helpers.py index 170120c..5c421cd 100644 --- a/v2realbot/strategyblocks/indicators/helpers.py +++ b/v2realbot/strategyblocks/indicators/helpers.py @@ -1,5 +1,5 @@ from v2realbot.utils.utils import isrising, isfalling,isfallingc, isrisingc, zoneNY, price2dec, print, safe_get, is_still, is_window_open, eval_cond_dict, crossed_down, crossed_up, crossed, is_pivot, json_serial, pct_diff, create_new_bars, slice_dict_lists -from v2realbot.strategy.base import StrategyState +#from v2realbot.strategy.base import StrategyState from traceback import format_exc #ZATIM tyto zkopirovany SEM DO HELPERS @@ -71,7 +71,7 @@ def get_source_or_MA(state, indicator): except KeyError: return state.bars[indicator] -def get_source_series(state: StrategyState, source: str): +def get_source_series(state, source: str): """ Podporujeme krome klice v bar a indikatoru a dalsi doplnujici, oddelene _ napr. dailyBars_close vezme serii static.dailyBars[close] diff --git a/v2realbot/strategyblocks/indicators/indicators_hub.py b/v2realbot/strategyblocks/indicators/indicators_hub.py index 5b9bb1e..6d45b59 100644 --- a/v2realbot/strategyblocks/indicators/indicators_hub.py +++ b/v2realbot/strategyblocks/indicators/indicators_hub.py @@ -55,20 +55,16 @@ def populate_all_indicators(data, state: StrategyState): #TODO tento lof patri spis do nextu classic SL - je poplatny typu stratefie #TODO na toto se podivam, nejak moc zajasonovani a zpatky - #PERF PROBLEM - state.ilog(lvl=1,e="ENTRY", msg=f"LP:{lp} P:{state.positions}/{round(float(state.avgp),3)} SL:{state.vars.activeTrade.stoploss_value if state.vars.activeTrade is not None else None} profit:{round(float(state.profit),2)} profit_rel:{round(np.mean(state.rel_profit_cum),6) if len(state.rel_profit_cum)>0 else 0} Trades:{len(state.tradeList)} pend:{state.vars.pending}", rel_profit_cum=str(state.rel_profit_cum), activeTrade=json.loads(json.dumps(state.vars.activeTrade, default=json_serial)), prescribedTrades=json.loads(json.dumps(state.vars.prescribedTrades, default=json_serial)), pending=str(state.vars.pending)) + state.ilog(lvl=1,e="ENTRY", msg=f"LP:{lp} P:{state.positions}/{round(float(state.avgp),3)} SL:{state.vars.activeTrade.stoploss_value if state.vars.activeTrade is not None else None} GP:{state.vars.activeTrade.goal_price if state.vars.activeTrade is not None else None} profit:{round(float(state.profit),2)} profit_rel:{round(np.mean(state.rel_profit_cum),6) if len(state.rel_profit_cum)>0 else 0} Trades:{len(state.tradeList)} pend:{state.vars.pending}", rel_profit_cum=str(state.rel_profit_cum), activeTrade=json.loads(json.dumps(state.vars.activeTrade, default=json_serial)), prescribedTrades=json.loads(json.dumps(state.vars.prescribedTrades, default=json_serial)), pending=str(state.vars.pending)) #kroky pro CONFIRMED BAR only if conf_bar == 1: - #logika pouze pro potvrzeny bar - state.ilog(lvl=0,e="BAR potvrzeny") - - #pri potvrzem CBARu nulujeme counter volume pro tick based indicator - state.vars.last_tick_volume = 0 - state.vars.next_new = 1 - #kroky pro CONTINOUS TICKS only + pass else: - #CBAR INDICATOR pro tick price a deltu VOLUME - populate_cbar_tick_price_indicator(data, state) + pass + + populate_cbar_tick_price_indicator(data, state) + #TBD nize predelat na typizovane RSI (a to jak na urovni CBAR tak confirmed) #populate_cbar_rsi_indicator() diff --git a/v2realbot/strategyblocks/newtrade/prescribedtrades.py b/v2realbot/strategyblocks/newtrade/prescribedtrades.py index 1c15227..443a17c 100644 --- a/v2realbot/strategyblocks/newtrade/prescribedtrades.py +++ b/v2realbot/strategyblocks/newtrade/prescribedtrades.py @@ -4,7 +4,8 @@ from v2realbot.utils.utils import zoneNY, json_serial from datetime import datetime #import random import json -from v2realbot.strategyblocks.activetrade.helpers import insert_SL_history, get_default_sl_value, normalize_tick +from v2realbot.strategyblocks.activetrade.helpers import insert_SL_history, get_default_sl_value, normalize_tick, get_profit_target_price +from v2realbot.strategyblocks.indicators.helpers import value_or_indicator #TODO nad prescribed trades postavit vstupni funkce def execute_prescribed_trades(state: StrategyState, data): @@ -46,15 +47,28 @@ def execute_prescribed_trades(state: StrategyState, data): res = state.buy(size=size) if isinstance(res, int) and res < 0: raise Exception(f"error in required operation LONG {res}") + + #defaultni goal price pripadne nastavujeme az v notifikaci + #TODO nastaveni SL az do notifikace, kdy je známá #pokud neni nastaveno SL v prescribe, tak nastavuji default dle stratvars if state.vars.activeTrade.stoploss_value is None: sl_defvalue = get_default_sl_value(state, direction=state.vars.activeTrade.direction) - #normalizuji dle aktualni ceny - sl_defvalue_normalized = normalize_tick(state, data,sl_defvalue) - state.vars.activeTrade.stoploss_value = float(data['close']) - sl_defvalue_normalized + + if isinstance(sl_defvalue, (float, int)): + #normalizuji dle aktualni ceny + sl_defvalue_normalized = normalize_tick(state, data,sl_defvalue) + state.vars.activeTrade.stoploss_value = float(data['close']) - sl_defvalue_normalized + state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue}, priced normalized: {sl_defvalue_normalized} price: {state.vars.activeTrade.stoploss_value }") + elif isinstance(sl_defvalue, str): + #from indicator + ind = sl_defvalue_abs + sl_defvalue_abs = float(value_or_indicator(state, sl_defvalue)) + if sl_defvalue_abs >= float(data['close']): + raise Exception(f"error in stoploss {sl_defvalue_abs} >= curr price") + state.vars.activeTrade.stoploss_value = sl_defvalue_abs + state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue_abs} dle indikatoru {ind}") insert_SL_history(state) - state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue}, priced normalized: {sl_defvalue_normalized} price: {state.vars.activeTrade.stoploss_value }") state.vars.pending = state.vars.activeTrade.id elif state.vars.activeTrade.direction == TradeDirection.SHORT: state.ilog(lvl=1,e="odesilame SHORT ORDER",trade=json.loads(json.dumps(state.vars.activeTrade, default=json_serial))) @@ -65,14 +79,26 @@ def execute_prescribed_trades(state: StrategyState, data): res = state.sell(size=size) if isinstance(res, int) and res < 0: raise Exception(f"error in required operation SHORT {res}") + #defaultní goalprice nastavujeme az v notifikaci + #pokud neni nastaveno SL v prescribe, tak nastavuji default dle stratvars if state.vars.activeTrade.stoploss_value is None: sl_defvalue = get_default_sl_value(state, direction=state.vars.activeTrade.direction) - #normalizuji dle aktualni ceny - sl_defvalue_normalized = normalize_tick(state, data, sl_defvalue) - state.vars.activeTrade.stoploss_value = float(data['close']) + sl_defvalue_normalized + + if isinstance(sl_defvalue, (float, int)): + #normalizuji dle aktualni ceny + sl_defvalue_normalized = normalize_tick(state, data,sl_defvalue) + state.vars.activeTrade.stoploss_value = float(data['close']) + sl_defvalue_normalized + state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue}, priced normalized: {sl_defvalue_normalized} price: {state.vars.activeTrade.stoploss_value }") + elif isinstance(sl_defvalue, str): + #from indicator + ind = sl_defvalue_abs + sl_defvalue_abs = float(value_or_indicator(state, sl_defvalue)) + if sl_defvalue_abs <= float(data['close']): + raise Exception(f"error in stoploss {sl_defvalue_abs} <= curr price") + state.vars.activeTrade.stoploss_value = sl_defvalue_abs + state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue_abs} dle indikatoru {ind}") insert_SL_history(state) - state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue}, priced normalized: {sl_defvalue_normalized} price: {state.vars.activeTrade.stoploss_value }") state.vars.pending = state.vars.activeTrade.id else: state.ilog(lvl=1,e="unknow direction")