From 7c1bba028ba03d79fab232b1fb269ab5f96b6354 Mon Sep 17 00:00:00 2001 From: David Brazda Date: Sun, 15 Oct 2023 20:14:44 +0200 Subject: [PATCH] enables day by day bt run, bt market premium+inds --- .gitignore | 9 + testy/funkce.py | 18 ++ v2realbot/backtesting/backtester.py | 8 +- v2realbot/common/db.py | 3 +- v2realbot/common/model.py | 9 + v2realbot/config.py | 17 ++ v2realbot/controller/services.py | 120 +++++++++---- v2realbot/main.py | 11 +- v2realbot/static/index.html | 13 ++ v2realbot/static/js/archivechart.js | 7 +- v2realbot/static/js/libs/atom-dark.css | 157 ++++++++++++++++++ v2realbot/static/main.css | 2 +- .../strategyblocks/activetrade/sl/trailsl.py | 17 +- .../indicators/custom/__init__.py | 11 +- .../indicators/custom/basestats.py | 26 +++ .../strategyblocks/indicators/custom/ema.py | 26 +++ .../indicators/custom/mathop.py | 9 +- .../indicators/custom/sameprice.py | 52 ++++++ .../indicators/{custom => }/custom_hub.py | 21 +-- .../strategyblocks/indicators/helpers.py | 11 +- .../indicators/indicators_hub.py | 2 +- .../strategyblocks/newtrade/conditions.py | 18 +- .../newtrade/prescribedtrades.py | 2 +- v2realbot/strategyblocks/newtrade/signals.py | 4 +- v2realbot/utils/utils.py | 10 +- 25 files changed, 500 insertions(+), 83 deletions(-) create mode 100644 testy/funkce.py create mode 100644 v2realbot/static/js/libs/atom-dark.css create mode 100644 v2realbot/strategyblocks/indicators/custom/ema.py create mode 100644 v2realbot/strategyblocks/indicators/custom/sameprice.py rename v2realbot/strategyblocks/indicators/{custom => }/custom_hub.py (92%) diff --git a/.gitignore b/.gitignore index 20dee60..a2fe170 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,12 @@ v2realbot/__pycache__/config.cpython-310.pyc .gitignore v2realbot/enums/__pycache__/enums.cpython-310.pyc v2realbot/common/__pycache__/model.cpython-310.pyc +v2realbot/__pycache__/config.cpython-310.pyc +v2realbot/backtesting/__pycache__/backtester.cpython-310.pyc +v2realbot/common/__pycache__/model.cpython-310.pyc +v2realbot/enums/__pycache__/enums.cpython-310.pyc +v2realbot/indicators/__pycache__/indicators.cpython-310.pyc +v2realbot/loader/__pycache__/aggregator.cpython-310.pyc +v2realbot/loader/__pycache__/trade_offline_streamer.cpython-310.pyc +v2realbot/loader/__pycache__/trade_ws_streamer.cpython-310.pyc +v2realbot/strategy/__pycache__/base.cpython-310.pyc diff --git a/testy/funkce.py b/testy/funkce.py new file mode 100644 index 0000000..df52005 --- /dev/null +++ b/testy/funkce.py @@ -0,0 +1,18 @@ +# import os + +# for filename in os.listdir("v2realbot/strategyblocks/indicators/custom"): +# print(filename) + +import os +import importlib +import v2realbot.strategyblocks.indicators.custom as ci + +ci.opengap.opengap() + +# for filename in os.listdir("v2realbot/strategyblocks/indicators/custom"): +# if filename.endswith(".py") and filename != "__init__.py": +# # __import__(filename[:-3]) +# #__import__(f"v2realbot.strategyblocks.indicators.custom.{filename[:-3]}") +# __import__(f"{filename[:-3]}") +# #importlib.import_module() + diff --git a/v2realbot/backtesting/backtester.py b/v2realbot/backtesting/backtester.py index fbb4273..0fced61 100644 --- a/v2realbot/backtesting/backtester.py +++ b/v2realbot/backtesting/backtester.py @@ -43,7 +43,7 @@ from v2realbot.common.model import TradeUpdate, Order #from rich import print import threading import asyncio -from v2realbot.config import BT_DELAYS, DATA_DIR, BT_FILL_CONDITION_BUY_LIMIT, BT_FILL_CONDITION_SELL_LIMIT, BT_FILL_LOG_SURROUNDING_TRADES, BT_FILL_CONS_TRADES_REQUIRED +from v2realbot.config import BT_DELAYS, DATA_DIR, BT_FILL_CONDITION_BUY_LIMIT, BT_FILL_CONDITION_SELL_LIMIT, BT_FILL_LOG_SURROUNDING_TRADES, BT_FILL_CONS_TRADES_REQUIRED,BT_FILL_PRICE_MARKET_ORDER_PREMIUM from v2realbot.utils.utils import AttributeDict, ltp, zoneNY, trunc, count_decimals, print from v2realbot.utils.tlog import tlog from v2realbot.enums.enums import FillCondition @@ -311,6 +311,12 @@ class Backtester: #ic(i) fill_time = i[0] fill_price = i[1] + #přičteme MARKET PREMIUM z konfigurace (do budoucna mozna rozdilne pro BUY/SELL a nebo mozna z konfigurace pro dany itutl) + if o.side == OrderSide.BUY: + fill_price = fill_price + BT_FILL_PRICE_MARKET_ORDER_PREMIUM + elif o.side == OrderSide.SELL: + fill_price = fill_price - BT_FILL_PRICE_MARKET_ORDER_PREMIUM + print("FILL ",o.side,"MARKET at", fill_time, datetime.fromtimestamp(fill_time).astimezone(zoneNY), "cena", i[1]) break else: diff --git a/v2realbot/common/db.py b/v2realbot/common/db.py index 1805f74..69ea6ce 100644 --- a/v2realbot/common/db.py +++ b/v2realbot/common/db.py @@ -2,7 +2,7 @@ from v2realbot.config import DATA_DIR import sqlite3 import queue import threading -from datetime import time +import time sqlite_db_file = DATA_DIR + "/v2trading.db" # Define the connection pool @@ -44,6 +44,7 @@ def execute_with_retry(cursor: sqlite3.Cursor, statement: str, retry_interval: i return cursor.execute(statement) except sqlite3.OperationalError as e: if str(e) == "database is locked": + print("database retry in 1s." + str(e)) time.sleep(retry_interval) continue else: diff --git a/v2realbot/common/model.py b/v2realbot/common/model.py index 67e91f5..3df0871 100644 --- a/v2realbot/common/model.py +++ b/v2realbot/common/model.py @@ -24,6 +24,15 @@ from alpaca.data.enums import Exchange # return user.id # raise HTTPException(status_code=404, detail=f"Could not find user with id: {id}") +class RunDay(BaseModel): + """ + Helper object for batch run - carries list of days in format required by run batch manager + """ + start: datetime + end: datetime + name: Optional[str] = None + note: Optional[str] = None + id: Optional[str] = None # Define a Pydantic model for input data class ConfigItem(BaseModel): diff --git a/v2realbot/config.py b/v2realbot/config.py index b5c17c9..2f56645 100644 --- a/v2realbot/config.py +++ b/v2realbot/config.py @@ -41,6 +41,23 @@ DATA_DIR = user_data_dir("v2realbot") PROFILING_NEXT_ENABLED = False PROFILING_OUTPUT_DIR = DATA_DIR +#FILL CONFIGURATION CLASS FOR BACKTESTING + +#WIP +class BT_FILL_CONF: + """" + Trida pro konfiguraci backtesting fillu pro dany symbol, pokud neexistuje tak fallback na obecny viz vyse- + + MOžná udělat i separátní profil PAPER/LIVE. Nějak vymyslet profily a jejich správa. + """ + def __init__(self, symbol, BT_FILL_CONS_TRADES_REQUIRED, BT_FILL_CONDITION_BUY_LIMIT, BT_FILL_CONDITION_SELL_LIMIT,BT_FILL_PRICE_MARKET_ORDER_PREMIUM): + self.symbol = symbol + self.BT_FILL_CONS_TRADES_REQUIRED = BT_FILL_CONS_TRADES_REQUIRED + self.BT_FILL_CONDITION_BUY_LIMIT=BT_FILL_CONDITION_BUY_LIMIT + self.BT_FILL_CONDITION_SELL_LIMIT=BT_FILL_CONDITION_SELL_LIMIT + self.BT_FILL_PRICE_MARKET_ORDER_PREMIUM=BT_FILL_PRICE_MARKET_ORDER_PREMIUM + + """" LATENCY DELAYS for LIVE eastcoast .000 trigger - last_trade_time (.4246266) diff --git a/v2realbot/controller/services.py b/v2realbot/controller/services.py index 9186c64..d98e0f3 100644 --- a/v2realbot/controller/services.py +++ b/v2realbot/controller/services.py @@ -6,20 +6,23 @@ from alpaca.data.requests import StockTradesRequest, StockBarsRequest from alpaca.data.enums import DataFeed from alpaca.data.timeframe import TimeFrame from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, OrderSide -from v2realbot.common.model import StrategyInstance, Runner, RunRequest, RunArchive, RunArchiveDetail, RunArchiveChange, Bar, TradeEvent, TestList, Intervals, ConfigItem +from v2realbot.common.model import RunDay, StrategyInstance, Runner, RunRequest, RunArchive, RunArchiveDetail, RunArchiveChange, Bar, TradeEvent, TestList, Intervals, ConfigItem from v2realbot.utils.utils import AttributeDict, zoneNY, dict_replace_value, Store, parse_toml_string, json_serial, is_open_hours, send_to_telegram from v2realbot.utils.ilog import delete_logs from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType from datetime import datetime from threading import Thread, current_thread, Event, enumerate -from v2realbot.config import STRATVARS_UNCHANGEABLES, ACCOUNT1_LIVE_API_KEY, ACCOUNT1_LIVE_SECRET_KEY, DATA_DIR,BT_FILL_CONS_TRADES_REQUIRED,BT_FILL_LOG_SURROUNDING_TRADES,BT_FILL_CONDITION_BUY_LIMIT,BT_FILL_CONDITION_SELL_LIMIT, GROUP_TRADES_WITH_TIMESTAMP_LESS_THAN +from v2realbot.config import STRATVARS_UNCHANGEABLES, ACCOUNT1_PAPER_API_KEY, ACCOUNT1_PAPER_SECRET_KEY, ACCOUNT1_LIVE_API_KEY, ACCOUNT1_LIVE_SECRET_KEY, DATA_DIR,BT_FILL_CONS_TRADES_REQUIRED,BT_FILL_LOG_SURROUNDING_TRADES,BT_FILL_CONDITION_BUY_LIMIT,BT_FILL_CONDITION_SELL_LIMIT, GROUP_TRADES_WITH_TIMESTAMP_LESS_THAN import importlib +from alpaca.trading.requests import GetCalendarRequest +from alpaca.trading.client import TradingClient +#from alpaca.trading.models import Calendar from queue import Queue from tinydb import TinyDB, Query, where from tinydb.operations import set import json from numpy import ndarray -#from rich import print +from rich import print import pandas as pd from traceback import format_exc from datetime import timedelta, time @@ -339,38 +342,93 @@ def get_testlist_byID(record_id: str): return 0, TestList(id=row[0], name=row[1], dates=json.loads(row[2])) +##TADY JSEM SKONCIL PROJIT - dodelat nastavni timezone +#nejspis vse v RunDays by melo byt jiz lokalizovano na zoneNY +#zaroven nejak vymyslet, aby bt_from/to uz bylo lokalizovano +#ted jsem dal natvrdo v main rest lokalizaci +#ale odtrasovat,ze vse funguje (nefunguje) + #volano pro batchove spousteni (BT,) def run_batch_stratin(id: UUID, runReq: RunRequest): - #pozor toto je test interval id (batch id se pak generuje pro kazdy davkovy run tohoto intervalu) - if runReq.test_batch_id is None: - return (-1, "batch_id required for batch run") - + #pozor test_batch_id je test interval id (batch id se pak generuje pro kazdy davkovy run tohoto intervalu) + if runReq.test_batch_id is None and (runReq.bt_from is None or runReq.bt_from.date() == runReq.bt_to.date()): + return (-1, "test interval or different days required for batch run") + if runReq.mode != Mode.BT: return (-1, "batch run only for backtest") - print("request values:", runReq) + #print("request values:", runReq) - print("getting intervals") - testlist: TestList + #getting days to run into RunDays format + if runReq.test_batch_id is not None: + print("getting intervals days") + testlist: TestList - res, testlist = get_testlist_byID(record_id=runReq.test_batch_id) + res, testlist = get_testlist_byID(record_id=runReq.test_batch_id) - if res < 0: - return (-1, f"not existing ID of testlists with {runReq.test_batch_id}") + if res < 0: + return (-1, f"not existing ID of testlists with {runReq.test_batch_id}") + + print("test interval:", testlist) + + cal_list = [] + #interval dame do formatu list(RunDays) + #TODO do budoucna predelat Interval na RunDays a na zone aware datetime + for intrvl in testlist.dates: + start_time = zoneNY.localize(datetime.fromisoformat(intrvl.start)) + end_time = zoneNY.localize(datetime.fromisoformat(intrvl.end)) + cal_list.append(RunDay(start = start_time, end = end_time, note=intrvl.note, id=testlist.id)) + + print(f"Getting intervals - RESULT: {cal_list}") + #sem getting dates + else: + #getting dates from calendat + clientTrading = TradingClient(ACCOUNT1_PAPER_API_KEY, ACCOUNT1_PAPER_SECRET_KEY, raw_data=False) + if runReq.bt_to is None: + runReq.bt_to = datetime.now().astimezone(zoneNY) + + calendar_request = GetCalendarRequest(start=runReq.bt_from,end=runReq.bt_to) + cal_dates = clientTrading.get_calendar(calendar_request) + #list(Calendar) + # Calendar + # date: date + # open: datetime + # close: datetime + cal_list = [] + for day in cal_dates: + start_time = zoneNY.localize(day.open) + end_time = zoneNY.localize(day.close) + + #u prvni polozky + if day == cal_dates[0]: + #pokud je cas od od vetsi nez open marketu prvniho dne, pouzijeme tento pozdejis cas + if runReq.bt_from > start_time: + start_time = runReq.bt_from + + #u posledni polozky + if day == cal_dates[-1]: + #cas do, je pred openenem market, nedavame tento den + if runReq.bt_to < start_time: + continue + #pokud koncovy cas neni do konce marketu, pouzijeme tento drivejsi namisto konce posledniho dne + if runReq.bt_to < end_time: + end_time = runReq.bt_to + cal_list.append(RunDay(start = start_time, end = end_time)) + + print(f"Getting interval dates from - to - RESULT: {cal_list}") #spousti se vlakno s paralelnim behem a vracime ok - ridici_vlakno = Thread(target=batch_run_manager, args=(id, runReq, testlist), name=f"Batch run controll thread started.") + ridici_vlakno = Thread(target=batch_run_manager, args=(id, runReq, cal_list), name=f"Batch run control thread started.") ridici_vlakno.start() print(enumerate()) return 0, f"Batch run started" - #thread, ktery bude ridit paralelni spousteni # bud ceka na dokonceni v runners nebo to bude ridit jinak a bude mit jednoho runnera? # nejak vymyslet. # logovani zatim jen do print -def batch_run_manager(id: UUID, runReq: RunRequest, testlist: TestList): +def batch_run_manager(id: UUID, runReq: RunRequest, rundays: list[RunDay]): #zde muzu iterovat nad intervaly #cekat az dobehne jeden interval a pak spustit druhy #pripadne naplanovat beh - to uvidim @@ -379,30 +437,21 @@ def batch_run_manager(id: UUID, runReq: RunRequest, testlist: TestList): #mohl podporovat i BATCH RUNy. batch_id = str(uuid4())[:8] runReq.batch_id = batch_id + print("Entering BATCH RUN MANAGER") print("generated batch_ID", batch_id) - print("test batch", testlist) - - print("test date", testlist.dates) - interval: Intervals - cnt_max = len(testlist.dates) + cnt_max = len(rundays) cnt = 0 #promenna pro sdileni mezi runy jednotlivych batchů (např. daily profit) inter_batch_params = dict(batch_profit=0) note_from_run_request = runReq.note - for intrvl in testlist.dates: + for day in rundays: cnt += 1 - interval = intrvl - if interval.note is not None: - print("mame zde note") - print("Datum od", interval.start) - print("Datum do", interval.end) - print("starting") - - #předání atributů datetime.fromisoformat - runReq.bt_from = datetime.fromisoformat(interval.start) - runReq.bt_to = datetime.fromisoformat(interval.end) - runReq.note = f"Batch {batch_id} #{cnt}/{cnt_max} {testlist.name} N:{interval.note} {note_from_run_request}" + print("Datum od", day.start) + print("Datum do", day.end) + runReq.bt_from = day.start + runReq.bt_to = day.end + runReq.note = f"Batch {batch_id} #{cnt}/{cnt_max} {day.name} N:{day.note} {note_from_run_request}" #protoze jsme v ridicim vlaknu, poustime za sebou jednotlive stratiny v synchronnim modu res, id_val = run_stratin(id=id, runReq=runReq, synchronous=True, inter_batch_params=inter_batch_params) @@ -434,9 +483,9 @@ def run_stratin(id: UUID, runReq: RunRequest, synchronous: bool = False, inter_b return (-1, "stratvars invalid") res, adp = parse_toml_string(i.add_data_conf) if res < 0: - return (-1, "add data conf invalid") - print("jsme uvnitr") + return (-1, "add data conf invalid") id = uuid4() + print(f"RUN {id} INITIATED") name = i.name symbol = i.symbol open_rush = i.open_rush @@ -1114,6 +1163,7 @@ def get_alpaca_history_bars(symbol: str, datetime_object_from: datetime, datetim #bars.data[symbol] return 0, result except Exception as e: + print(str(e) + format_exc()) return -2, str(e) # change_archived_runner diff --git a/v2realbot/main.py b/v2realbot/main.py index 007a48d..05ccd33 100644 --- a/v2realbot/main.py +++ b/v2realbot/main.py @@ -24,7 +24,7 @@ from queue import Queue, Empty from threading import Thread import asyncio from v2realbot.common.db import insert_queue, insert_conn, pool -from v2realbot.utils.utils import json_serial, send_to_telegram +from v2realbot.utils.utils import json_serial, send_to_telegram, zoneNY, zonePRG from uuid import uuid4 from sqlite3 import OperationalError from time import sleep @@ -226,7 +226,14 @@ def _get_stratin(stratin_id) -> StrategyInstance: @app.put("/stratins/{stratin_id}/run", dependencies=[Depends(api_key_auth)], status_code=status.HTTP_200_OK) def _run_stratin(stratin_id: UUID, runReq: RunRequest): #print(runReq) - if runReq.test_batch_id is not None: + if runReq.bt_from is not None: + runReq.bt_from = zonePRG.localize(runReq.bt_from) + + if runReq.bt_to is not None: + runReq.bt_to = zonePRG.localize(runReq.bt_to) + #pokud jedeme nad test intervaly anebo je požadováno více dní - pouštíme jako batch day by day + #do budoucna dát na FE jako flag + if runReq.test_batch_id is not None or (runReq.bt_from.date() != runReq.bt_to.date()): res, id = cs.run_batch_stratin(id=stratin_id, runReq=runReq) else: res, id = cs.run_stratin(id=stratin_id, runReq=runReq) diff --git a/v2realbot/static/index.html b/v2realbot/static/index.html index 6c40716..9c4a3b1 100644 --- a/v2realbot/static/index.html +++ b/v2realbot/static/index.html @@ -82,6 +82,19 @@ + + diff --git a/v2realbot/static/js/archivechart.js b/v2realbot/static/js/archivechart.js index e74aca3..cabae2a 100644 --- a/v2realbot/static/js/archivechart.js +++ b/v2realbot/static/js/archivechart.js @@ -298,6 +298,8 @@ function prepare_data(archRunner, timeframe_amount, timeframe_unit, archivedRunn //$("#statusStratvars").text(JSON.stringify(data.stratvars,null,2)) }, error: function(xhr, status, error) { + oneMinuteBars = null + chart_archived_run(archRunner, archivedRunnerDetail, oneMinuteBars); var err = eval("(" + xhr.responseText + ")"); window.alert(JSON.stringify(xhr)); console.log(JSON.stringify(xhr)); @@ -357,6 +359,7 @@ function chart_archived_run(archRecord, data, oneMinuteBars) { candlestickSeries = null + //v pripade, ze neprojde get bars, nastavit na intervals[0] switch_to_interval(intervals[1]) chart.timeScale().fitContent(); @@ -963,11 +966,11 @@ function display_log(iterLogList, timestamp) { highlighted = (parseInt(logLine.time) == parseInt(timestamp)) ? "highlighted" : "" logcnt++; - row = '
'+logLine.time + " " + logLine.event + ' - '+ (logLine.message == undefined ? "" : logLine.message) +'
' + row = '
'+logLine.time + " " + logLine.event + ' - '+ (logLine.message == undefined ? "" : logLine.message) +'
' str_row = JSON.stringify(logLine.details, null, 2) //row_detail = '
' + str_row + '
' - row_detail = '
' + str_row + '
' + row_detail = '
' + str_row + '
' var lines = document.getElementById('lines') var line = document.createElement('div') diff --git a/v2realbot/static/js/libs/atom-dark.css b/v2realbot/static/js/libs/atom-dark.css new file mode 100644 index 0000000..de8ef1a --- /dev/null +++ b/v2realbot/static/js/libs/atom-dark.css @@ -0,0 +1,157 @@ +[class*=shj-lang-] { + white-space: pre; + /* margin: 10px 0; */ + /* border-radius: 10px; */ + /* padding: 30px 20px; */ + background: white; + color: #112; + box-shadow: 0 0 5px #0001; + text-shadow: none; + font: 11px Consolas,Courier New,Monaco,Andale Mono,Ubuntu Mono,monospace; + line-height: 15px; + box-sizing: border-box; + /* max-width: min(100%,100vw); */ + word-wrap: normal; +} + +.shj-inline { + margin: 0; + padding: 2px 5px; + display: inline-block; + border-radius: 5px +} + +[class*=shj-lang-]::selection,[class*=shj-lang-] ::selection { + background: #bdf5 +} + +[class*=shj-lang-]>div { + display: flex; + overflow: auto +} + +[class*=shj-lang-]>div :last-child { + flex: 1; + outline: none +} + +.shj-numbers { + padding-left: 5px; + counter-reset: line +} + +.shj-numbers div { + padding-right: 5px +} + +.shj-numbers div:before { + color: #999; + display: block; + content: counter(line); + opacity: .5; + text-align: right; + margin-right: 5px; + counter-increment: line +} + +.shj-syn-cmnt { + font-style: italic +} + +.shj-syn-err,.shj-syn-kwd { + color: #e16 +} + +.shj-syn-num,.shj-syn-class { + color: #f60 +} + +.shj-numbers,.shj-syn-cmnt { + color: #999 +} + +.shj-syn-insert,.shj-syn-str { + color: #7d8 +} + +.shj-syn-bool { + color: #3bf +} + +.shj-syn-type,.shj-syn-oper { + color: #5af +} + +.shj-syn-section,.shj-syn-func { + color: #84f +} + +.shj-syn-deleted,.shj-syn-var { + color: #f44 +} + +.shj-oneline { + padding: 12px 10px +} + +.shj-lang-http.shj-oneline .shj-syn-kwd { + background: #25f; + color: #fff; + padding: 5px 7px; + border-radius: 5px +} + +.shj-multiline.shj-mode-header { + padding: 20px +} + +.shj-multiline.shj-mode-header:before { + content: attr(data-lang); + color: #58f; + display: block; + padding: 10px 20px; + background: #58f3; + border-radius: 5px; + margin-bottom: 20px +} + +[class*=shj-lang-] { + color: #abb2bf; + background: #161b22 +} + +[class*=shj-lang-]:before { + color: #6f9aff +} + +.shj-syn-deleted,.shj-syn-err,.shj-syn-var { + color: #e06c75 +} + +.shj-syn-section,.shj-syn-oper,.shj-syn-kwd { + color: #c678dd +} + +.shj-syn-class { + color: #e5c07b +} + +.shj-numbers,.shj-syn-cmnt { + color: #76839a +} + +.shj-syn-insert { + color: #98c379 +} + +.shj-syn-type { + color: #56b6c2 +} + +.shj-syn-num,.shj-syn-bool { + color: #d19a66 +} + +.shj-syn-str,.shj-syn-func { + color: #61afef +} diff --git a/v2realbot/static/main.css b/v2realbot/static/main.css index 5d6eccf..f694729 100644 --- a/v2realbot/static/main.css +++ b/v2realbot/static/main.css @@ -278,7 +278,7 @@ html { height: 568px; flex-direction: column-reverse; margin-left: 28px; - width: 100%; + width: 130%; } #lowercontainer { diff --git a/v2realbot/strategyblocks/activetrade/sl/trailsl.py b/v2realbot/strategyblocks/activetrade/sl/trailsl.py index aa0a5ed..ea510e3 100644 --- a/v2realbot/strategyblocks/activetrade/sl/trailsl.py +++ b/v2realbot/strategyblocks/activetrade/sl/trailsl.py @@ -63,27 +63,30 @@ def trail_SL_management(state: StrategyState, data): def_SL = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(options, directive_name, 0.01)) directive_name = "SL_trailing_offset_"+str(smer) offset = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(options, directive_name, 0.01)) + directive_name = "SL_trailing_step_"+str(smer) + step = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(options, directive_name, offset)) #pokud je pozadovan trail jen do breakeven a uz prekroceno if (direction == TradeDirection.LONG and stop_breakeven and state.vars.activeTrade.stoploss_value >= float(state.avgp)) or (direction == TradeDirection.SHORT and stop_breakeven and state.vars.activeTrade.stoploss_value <= float(state.avgp)): state.ilog(lvl=1,e=f"SL trail STOP at breakeven {str(smer)} SL:{state.vars.activeTrade.stoploss_value} UNCHANGED", stop_breakeven=stop_breakeven) return - #IDEA: Nyni posouvame SL o offset, mozna ji posunout jen o direktivu step ? + #Aktivace SL pokud vystoupa na "offset", a nasledne posunuti o "step" offset_normalized = normalize_tick(state, data, offset) #to ticks and from options + step_normalized = normalize_tick(state, data, step) def_SL_normalized = normalize_tick(state, data, def_SL) if direction == TradeDirection.LONG: move_SL_threshold = state.vars.activeTrade.stoploss_value + offset_normalized + def_SL_normalized - state.ilog(lvl=1,e=f"SL TRAIL EVAL {smer} SL:{round(state.vars.activeTrade.stoploss_value,3)} TRAILGOAL:{move_SL_threshold}", def_SL=def_SL, offset=offset, offset_normalized=offset_normalized, def_SL_normalized=def_SL_normalized) + state.ilog(lvl=1,e=f"SL TRAIL EVAL {smer} SL:{round(state.vars.activeTrade.stoploss_value,3)} TRAILGOAL:{move_SL_threshold}", def_SL=def_SL, offset=offset, offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized) if (move_SL_threshold) < data['close']: - state.vars.activeTrade.stoploss_value += offset_normalized + state.vars.activeTrade.stoploss_value += step_normalized insert_SL_history(state) - state.ilog(lvl=1,e=f"SL TRAIL TH {smer} reached {move_SL_threshold} SL moved to {state.vars.activeTrade.stoploss_value}", offset_normalized=offset_normalized, def_SL_normalized=def_SL_normalized) + state.ilog(lvl=1,e=f"SL TRAIL TH {smer} reached {move_SL_threshold} SL moved to {state.vars.activeTrade.stoploss_value}", offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized) elif direction == TradeDirection.SHORT: move_SL_threshold = state.vars.activeTrade.stoploss_value - offset_normalized - def_SL_normalized - state.ilog(lvl=0,e=f"SL TRAIL EVAL {smer} SL:{round(state.vars.activeTrade.stoploss_value,3)} TRAILGOAL:{move_SL_threshold}", def_SL=def_SL, offset=offset, offset_normalized=offset_normalized, def_SL_normalized=def_SL_normalized) + state.ilog(lvl=0,e=f"SL TRAIL EVAL {smer} SL:{round(state.vars.activeTrade.stoploss_value,3)} TRAILGOAL:{move_SL_threshold}", def_SL=def_SL, offset=offset, offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized) if (move_SL_threshold) > data['close']: - state.vars.activeTrade.stoploss_value -= offset_normalized + state.vars.activeTrade.stoploss_value -= step_normalized insert_SL_history(state) - state.ilog(lvl=1,e=f"SL TRAIL GOAL {smer} reached {move_SL_threshold} SL moved to {state.vars.activeTrade.stoploss_value}", offset_normalized=offset_normalized, def_SL_normalized=def_SL_normalized) + state.ilog(lvl=1,e=f"SL TRAIL GOAL {smer} reached {move_SL_threshold} SL moved to {state.vars.activeTrade.stoploss_value}", offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized) diff --git a/v2realbot/strategyblocks/indicators/custom/__init__.py b/v2realbot/strategyblocks/indicators/custom/__init__.py index b6e690f..b71e431 100644 --- a/v2realbot/strategyblocks/indicators/custom/__init__.py +++ b/v2realbot/strategyblocks/indicators/custom/__init__.py @@ -1 +1,10 @@ -from . import * +import os +# import importlib +# from v2realbot.strategyblocks.indicators.custom.opengap import opengap + +for filename in os.listdir("v2realbot/strategyblocks/indicators/custom"): + if filename.endswith(".py") and filename != "__init__.py": + # __import__(filename[:-3]) + __import__(f"v2realbot.strategyblocks.indicators.custom.{filename[:-3]}") + #importlib.import_module() + diff --git a/v2realbot/strategyblocks/indicators/custom/basestats.py b/v2realbot/strategyblocks/indicators/custom/basestats.py index 142ec4c..50d8cf2 100644 --- a/v2realbot/strategyblocks/indicators/custom/basestats.py +++ b/v2realbot/strategyblocks/indicators/custom/basestats.py @@ -35,6 +35,32 @@ def basestats(state, params): val = np.amax(source_array) elif func == "mean": val = np.mean(source_array) + elif func == "var": + data = np.array(source_array) + mean_value = np.mean(data) + # Calculate the variance of the data + val = np.mean((data - mean_value) ** 2) + elif func == "angle": + delka_pole = len(source_array) + if delka_pole < 2: + return 0,0 + + x = np.arange(delka_pole) + y = np.array(source_array) + + # Fit a linear polynomial to the data + coeffs = np.polyfit(x, y, 1) + + # Calculate the angle in radians angle_rad + val = np.arctan(coeffs[0]) * 1000 + + # Convert the angle to degrees angle_deg + #angle_deg = np.degrees(angle_rad) + + # Normalize the degrees between -1 and 1 + #val = 2 * (angle_deg / 180) - 1 + elif func =="stdev": + val = np.std(source_array) else: return -2, "wrong function" diff --git a/v2realbot/strategyblocks/indicators/custom/ema.py b/v2realbot/strategyblocks/indicators/custom/ema.py new file mode 100644 index 0000000..d0a8693 --- /dev/null +++ b/v2realbot/strategyblocks/indicators/custom/ema.py @@ -0,0 +1,26 @@ +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.strategy.base import StrategyState +from v2realbot.indicators.indicators import ema as ext_ema +from v2realbot.strategyblocks.indicators.helpers import get_source_series +from rich import print as printanyway +from traceback import format_exc +from v2realbot.ml.ml import ModelML +import numpy as np +from collections import defaultdict +from v2realbot.strategyblocks.indicators.helpers import value_or_indicator +#strength, absolute change of parameter between current value and lookback value (n-past) +#used for example to measure unusual peaks +def ema(state, params): + funcName = "ema" + source = safe_get(params, "source", None) + lookback = safe_get(params, "lookback",14) + + #lookback muze byt odkaz na indikator, pak berem jeho hodnotu + lookback = int(value_or_indicator(state, lookback)) + + source_series = get_source_series(state, source)[-lookback:] + ema_value = ext_ema(source_series, lookback) + val = round(ema_value[-1],4) + + state.ilog(lvl=1,e=f"INSIDE {funcName} {val} {source=} {lookback=}", **params) + return 0, val \ No newline at end of file diff --git a/v2realbot/strategyblocks/indicators/custom/mathop.py b/v2realbot/strategyblocks/indicators/custom/mathop.py index f342789..1828aae 100644 --- a/v2realbot/strategyblocks/indicators/custom/mathop.py +++ b/v2realbot/strategyblocks/indicators/custom/mathop.py @@ -15,13 +15,16 @@ def mathop(state, params): if source1 is None or source2 is None or operator is None: return -2, "required source1 source2 operator" + druhy = float(value_or_indicator(state, source2)) if operator == "+": - val = round(float(source1_series[-1] + value_or_indicator(state, source2)),4) + val = round(float(source1_series[-1] + druhy),4) elif operator == "-": - val = round(float(source1_series[-1] - value_or_indicator(state, source2)),4) + val = round(float(source1_series[-1] - druhy),4) + elif operator == "*": + val = round(float(source1_series[-1] * druhy),4) else: return -2, "unknow operator" - #state.ilog(lvl=0,e=f"INSIDE {funcName} {source1=} {source2=} {val}", **params) + state.ilog(lvl=1,e=f"INSIDE {funcName} {source1=} {source2=} {val} {druhy=}", **params) return 0, val diff --git a/v2realbot/strategyblocks/indicators/custom/sameprice.py b/v2realbot/strategyblocks/indicators/custom/sameprice.py new file mode 100644 index 0000000..696060d --- /dev/null +++ b/v2realbot/strategyblocks/indicators/custom/sameprice.py @@ -0,0 +1,52 @@ +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.strategy.base import StrategyState +from v2realbot.indicators.indicators import ema, natr, roc +from v2realbot.strategyblocks.indicators.helpers import get_source_series +from rich import print as printanyway +from traceback import format_exc +from v2realbot.ml.ml import ModelML +import numpy as np +from collections import defaultdict +import bisect + +#strength, absolute change of parameter between current value and lookback value (n-past) +#used for example to measure unusual peaks +def sameprice(state, params): + funcName = "sameprice" + typ = safe_get(params, "type", None) + + def find_first_bigger_than_lastitem_backwards(list1): + last_item = list1[-1] + for i in range(len(list1) - 2, -1, -1): + if list1[i] > last_item: + return i + return -1 + + def find_first_smaller_than_lastitem_backwards(list1): + last_item = list1[-1] + for i in range(len(list1) - 2, -1, -1): + if list1[i] < last_item: + return i + return -1 + + if typ == "up": + pozice_prvniho_vetsiho = find_first_bigger_than_lastitem_backwards(state.bars["vwap"]) + elif typ == "down": + pozice_prvniho_vetsiho = find_first_smaller_than_lastitem_backwards(state.bars["vwap"]) + else: + return -2, "unknow type" + + celkova_delka = len(state.bars["vwap"]) + + #jde o daily high + if pozice_prvniho_vetsiho == -1: + state.ilog(lvl=1,e=f"INSIDE {funcName} {typ} {pozice_prvniho_vetsiho=} vracime 1") + return 0, celkova_delka + + delka_k_predchozmu = celkova_delka - pozice_prvniho_vetsiho + normalizovano = delka_k_predchozmu/celkova_delka + + state.ilog(lvl=1,e=f"INSIDE {funcName} {typ} {pozice_prvniho_vetsiho=} {celkova_delka=} {delka_k_predchozmu=} {normalizovano=}", pozice_prvniho_vetsiho=pozice_prvniho_vetsiho, celkova_delka=celkova_delka, delka_k_predchozmu=delka_k_predchozmu, **params) + + return 0, delka_k_predchozmu + diff --git a/v2realbot/strategyblocks/indicators/custom/custom_hub.py b/v2realbot/strategyblocks/indicators/custom_hub.py similarity index 92% rename from v2realbot/strategyblocks/indicators/custom/custom_hub.py rename to v2realbot/strategyblocks/indicators/custom_hub.py index 092c46a..db6a1af 100644 --- a/v2realbot/strategyblocks/indicators/custom/custom_hub.py +++ b/v2realbot/strategyblocks/indicators/custom_hub.py @@ -8,15 +8,16 @@ import importlib #TODO TENTO IMPORT VYMYSLET, abych naloadoval package custom a nemusel nic pridat (vymyslet dynamicke volani z cele package ci) #from v2realbot.strategyblocks.indicators.custom._upscaled_rsi_wip import upscaledrsi -from v2realbot.strategyblocks.indicators.custom.barparams import barparams -from v2realbot.strategyblocks.indicators.custom.basestats import basestats -from v2realbot.strategyblocks.indicators.custom.delta import delta -from v2realbot.strategyblocks.indicators.custom.divergence import divergence -from v2realbot.strategyblocks.indicators.custom.model import model -from v2realbot.strategyblocks.indicators.custom.opengap import opengap -from v2realbot.strategyblocks.indicators.custom.slope import slope -from v2realbot.strategyblocks.indicators.custom.conditional import conditional -from v2realbot.strategyblocks.indicators.custom.mathop import mathop +import v2realbot.strategyblocks.indicators.custom as ci +# from v2realbot.strategyblocks.indicators.custom.barparams import barparams +# from v2realbot.strategyblocks.indicators.custom.basestats import basestats +# from v2realbot.strategyblocks.indicators.custom.delta import delta +# from v2realbot.strategyblocks.indicators.custom.divergence import divergence +# from v2realbot.strategyblocks.indicators.custom.model import model +# from v2realbot.strategyblocks.indicators.custom.opengap import opengap +# from v2realbot.strategyblocks.indicators.custom.slope import slope +# from v2realbot.strategyblocks.indicators.custom.conditional import conditional +# from v2realbot.strategyblocks.indicators.custom.mathop import mathop # import v2realbot.strategyblocks.indicators.custom as ci @@ -143,7 +144,7 @@ def populate_dynamic_custom_indicator(data, state: StrategyState, name): #pozor jako defaultní hodnotu dává engine 0 - je to ok? try: - #subtype = "ci."+subtype + subtype = "ci."+subtype+"."+subtype custom_function = eval(subtype) res_code, new_val = custom_function(state, custom_params) if res_code == 0: diff --git a/v2realbot/strategyblocks/indicators/helpers.py b/v2realbot/strategyblocks/indicators/helpers.py index 04c1c76..27aec3b 100644 --- a/v2realbot/strategyblocks/indicators/helpers.py +++ b/v2realbot/strategyblocks/indicators/helpers.py @@ -1,4 +1,4 @@ -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.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 traceback import format_exc @@ -9,13 +9,16 @@ from traceback import format_exc def value_or_indicator(state,value): #preklad direktivy podle typu, pokud je int anebo float - je to primo hodnota #pokud je str, jde o indikator a dotahujeme posledni hodnotu z nej - if isinstance(value, (int, float)): + if isinstance(value, (float, int)): return value elif isinstance(value, str): try: #pokud existuje v indikatoru MA bereme MA jinak indikator, pokud neexistuje bereme bar ret = get_source_or_MA(state, indicator=value)[-1] - state.ilog(lvl=0,e=f"Pro porovnani bereme posledni hodnotu {ret} z indikatoru {value}") + lvl = 0 + if ret == 0: + lvl = 1 + state.ilog(lvl=lvl,e=f"Pro porovnani bereme posledni hodnotu {ret} z indikatoru {value}") except Exception as e : ret = 0 state.ilog(lvl=1,e=f"Neexistuje indikator s nazvem {value} vracime 0" + str(e) + format_exc()) @@ -38,6 +41,8 @@ def evaluate_directive_conditions(state, work_dict, cond_type): "above": lambda ind, val: get_source_or_MA(state, ind)[-1] > value_or_indicator(state,val), "equals": lambda ind, val: get_source_or_MA(state, ind)[-1] == value_or_indicator(state,val), "below": lambda ind, val: get_source_or_MA(state, ind)[-1] < value_or_indicator(state,val), + "fallingc": lambda ind, val: isfallingc(get_source_or_MA(state, ind), val), + "risingc": lambda ind, val: isrisingc(get_source_or_MA(state, ind), val), "falling": lambda ind, val: isfalling(get_source_or_MA(state, ind), val), "rising": lambda ind, val: isrising(get_source_or_MA(state, ind), val), "crossed_down": lambda ind, val: buy_if_crossed_down(state, ind, value_or_indicator(state,val)), diff --git a/v2realbot/strategyblocks/indicators/indicators_hub.py b/v2realbot/strategyblocks/indicators/indicators_hub.py index b62b158..cd899d7 100644 --- a/v2realbot/strategyblocks/indicators/indicators_hub.py +++ b/v2realbot/strategyblocks/indicators/indicators_hub.py @@ -1,7 +1,7 @@ from v2realbot.strategy.base import StrategyState from v2realbot.strategyblocks.indicators.cbar_price import populate_cbar_tick_price_indicator -from v2realbot.strategyblocks.indicators.custom.custom_hub import populate_dynamic_custom_indicator +from v2realbot.strategyblocks.indicators.custom_hub import populate_dynamic_custom_indicator from v2realbot.strategyblocks.indicators.slope import populate_dynamic_slope_indicator from v2realbot.strategyblocks.indicators.slopeLP import populate_dynamic_slopeLP_indicator from v2realbot.strategyblocks.indicators.ema import populate_dynamic_ema_indicator diff --git a/v2realbot/strategyblocks/newtrade/conditions.py b/v2realbot/strategyblocks/newtrade/conditions.py index bfe78c0..b60c82f 100644 --- a/v2realbot/strategyblocks/newtrade/conditions.py +++ b/v2realbot/strategyblocks/newtrade/conditions.py @@ -33,18 +33,18 @@ def go_conditions_met(state, data, signalname: str, direction: TradeDirection): #dont_buy_above = value nebo hazev indikatoru #TESTUJEME SPECIFICKY DONT_GO - - #u techto ma smysl pouze OR + #jak OR tak i AND cond_dict = state.vars.conditions[KW.dont_go][signalname][smer] result, conditions_met = evaluate_directive_conditions(state, cond_dict, "OR") - state.ilog(lvl=1,e=f"SPECIFIC PRECOND {smer} {result}", **conditions_met, cond_dict=cond_dict) + state.ilog(lvl=1,e=f"SPECIFIC PRECOND =OR= {smer} {result}", **conditions_met, cond_dict=cond_dict) + if result: + return False + + #OR neprosly testujeme AND + result, conditions_met = evaluate_directive_conditions(state, cond_dict, "AND") + state.ilog(lvl=1,e=f"SPECIFIC PRECOND =AND={smer} {result}", **conditions_met, cond_dict=cond_dict) if result: return False - - # #OR neprosly testujeme AND - # result, conditions_met = evaluate_directive_conditions(cond_dict, "AND") - # state.ilog(lvl=0,e=f"EXIT CONDITIONS of activeTrade {smer} =AND= {result}", **conditions_met, cond_dict=cond_dict) - # if result: - # return True #tyto timto nahrazeny - dat do konfigurace (dont_short_when, dont_long_when) #dont_buy_when['rsi_too_high'] = state.indicators.RSI14[-1] > safe_get(state.vars, "rsi_dont_buy_above",50) @@ -110,7 +110,7 @@ def common_go_preconditions_check(state, data, signalname: str, options: dict): if state.vars.last_exit_index is not None: index_to_compare = int(state.vars.last_exit_index)+int(next_signal_offset) if index_to_compare > int(data["index"]): - state.ilog(lvl=1,e=f"NEXT SIGNAL OFFSET from EXIT {next_signal_offset} waiting - TOO SOON", currindex=data["index"], index_to_compare=index_to_compare, last_exit_index=state.vars.last_exit_index) + state.ilog(lvl=1,e=f"NEXT SIGNAL OFFSET from EXIT {next_signal_offset} waiting - TOO SOON {signalname}", currindex=data["index"], index_to_compare=index_to_compare, last_exit_index=state.vars.last_exit_index) return False # if is_open_rush(datetime.fromtimestamp(data['updated']).astimezone(zoneNY), open_rush) or is_close_rush(datetime.fromtimestamp(data['updated']).astimezone(zoneNY), close_rush): diff --git a/v2realbot/strategyblocks/newtrade/prescribedtrades.py b/v2realbot/strategyblocks/newtrade/prescribedtrades.py index 4730576..d4637cd 100644 --- a/v2realbot/strategyblocks/newtrade/prescribedtrades.py +++ b/v2realbot/strategyblocks/newtrade/prescribedtrades.py @@ -45,7 +45,7 @@ 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}") - #nastaveni SL az do notifikace, kdy je známá + #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) diff --git a/v2realbot/strategyblocks/newtrade/signals.py b/v2realbot/strategyblocks/newtrade/signals.py index 217e3dc..b9b1040 100644 --- a/v2realbot/strategyblocks/newtrade/signals.py +++ b/v2realbot/strategyblocks/newtrade/signals.py @@ -38,7 +38,7 @@ def signal_search(state: StrategyState, data): # to vse za predpokladu, ze neni aktivni trade def execute_signal_generator(state, data, name): - state.ilog(lvl=0,e=f"SIGNAL SEARCH for {name}", cond_go=state.vars.conditions[KW.go][name], cond_dontgo=state.vars.conditions[KW.dont_go][name], cond_activate=state.vars.conditions[KW.activate][name] ) + state.ilog(lvl=1,e=f"SIGNAL SEARCH for {name}", cond_go=state.vars.conditions[KW.go][name], cond_dontgo=state.vars.conditions[KW.dont_go][name], cond_activate=state.vars.conditions[KW.activate][name] ) options = safe_get(state.vars.signals, name, None) if options is None: @@ -60,7 +60,7 @@ def execute_signal_generator(state, data, name): custom_function = eval(signal_plugin) custom_function() except NameError: - state.ilog(lvl=1,e="Custom plugin {signal_plugin} not found") + state.ilog(lvl=1,e=f"Custom plugin {signal_plugin} not found") else: short_enabled = safe_get(options, "short_enabled",safe_get(state.vars, "short_enabled",True)) long_enabled = safe_get(options, "long_enabled",safe_get(state.vars, "long_enabled",True)) diff --git a/v2realbot/utils/utils.py b/v2realbot/utils/utils.py index 51d195d..875e272 100644 --- a/v2realbot/utils/utils.py +++ b/v2realbot/utils/utils.py @@ -408,6 +408,8 @@ qu = Queue() #zoneNY = tz.gettz('America/New_York') zoneNY = pytz.timezone('US/Eastern') +zonePRG = pytz.timezone('Europe/Amsterdam') + def print(*args, **kwargs): if QUIET_MODE: pass @@ -567,8 +569,8 @@ def is_open_hours(dt, business_hours: dict = None): and dt.date() not in holidays \ and business_hours["from"] <= dt.time() < business_hours["to"] -#vraci zda dane pole je klesajici (bud cele a nebo jen pocet poslednich) -def isfalling_old(pole: list, pocet: int = None): +#vraci zda dane pole je klesajici (bud cele a nebo jen pocet poslednich) - no same values +def isfallingc(pole: list, pocet: int = None): if pocet is None: pocet = len(pole) if len(pole) j for i, j in zip(new_pole, new_pole[1:])) return res -#vraci zda dane pole je roustouci (bud cele a nebo jen pocet poslednich) -def isrising_old(pole: list, pocet: int = None): +#vraci zda dane pole je roustouci (bud cele a nebo jen pocet poslednich) - no same values +def isrisingc(pole: list, pocet: int = None): if pocet is None: pocet = len(pole) if len(pole)