diff --git a/v2realbot/ENTRY_backtest_strategyVykladaci.py b/v2realbot/ENTRY_backtest_strategyVykladaci.py index b7f4097..c120908 100644 --- a/v2realbot/ENTRY_backtest_strategyVykladaci.py +++ b/v2realbot/ENTRY_backtest_strategyVykladaci.py @@ -4,7 +4,7 @@ from v2realbot.strategy.base import StrategyState from v2realbot.strategy.StrategyOrderLimitVykladaci import StrategyOrderLimitVykladaci from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, OrderSide from v2realbot.indicators.indicators import ema -from v2realbot.utils.utils import ltp, isrising, isfalling,trunc,AttributeDict, zoneNY, price2dec, dict_replace_value, print +from v2realbot.utils.utils import ltp, isrising, isfalling,trunc,AttributeDict, zoneNY, price2dec, dict_replace_value, print, safe_get from datetime import datetime from icecream import install, ic #from rich import print @@ -51,6 +51,7 @@ stratvars = AttributeDict(maxpozic = 400, slope_lookback = 300, lookback_offset = 20, minimum_slope = -0.05, + first_buy_market = False ) ##toto rozparsovat a strategii spustit stejne jako v main toml_string = """ @@ -129,7 +130,12 @@ def next(data, state: StrategyState): price = last_price state.ilog(e="BUY Vykladame", msg="first price"+str(price) + "pozic:"+str(vykladka), curve=curve, ema=state.indicators.ema[-1], trend=state.vars.Trend, price=price, vykladka=vykladka) ##prvni se vyklada na aktualni cenu, další jdou podle krivky, nula v krivce zvyšuje množství pro následující iteraci - state.buy_l(price=price, size=qty) + + ##VAR - na zaklade conf. muzeme jako prvni posilat MARKET + if safe_get(state.vars, "first_buy_market") == True: + state.buy(size=qty) + else: + state.buy_l(price=price, size=qty) print("prvni limitka na aktuální cenu. Další podle křivky", price, qty) for i in range(0,vykladka-1): price = price2dec(float(price - curve[i])) diff --git a/v2realbot/__pycache__/config.cpython-310.pyc b/v2realbot/__pycache__/config.cpython-310.pyc index 8a758b4..330c508 100644 Binary files a/v2realbot/__pycache__/config.cpython-310.pyc and b/v2realbot/__pycache__/config.cpython-310.pyc differ diff --git a/v2realbot/backtesting/__pycache__/backtester.cpython-310.pyc b/v2realbot/backtesting/__pycache__/backtester.cpython-310.pyc index 7a34232..d6e5ed3 100644 Binary files a/v2realbot/backtesting/__pycache__/backtester.cpython-310.pyc and b/v2realbot/backtesting/__pycache__/backtester.cpython-310.pyc differ diff --git a/v2realbot/backtesting/backtester.py b/v2realbot/backtesting/backtester.py index 0339a75..2fcbbb2 100644 --- a/v2realbot/backtesting/backtester.py +++ b/v2realbot/backtesting/backtester.py @@ -43,8 +43,8 @@ from v2realbot.common.model import TradeUpdate, Order #from rich import print import threading import asyncio -from v2realbot.config import BT_DELAYS, DATA_DIR, FILL_CONDITION_BUY_LIMIT, FILL_CONDITION_SELL_LIMIT, FILL_LOG_SURROUNDING_TRADES, FILL_CONS_TRADES_REQUIRED -from v2realbot.utils.utils import AttributeDict, ltp, zoneNY, trunc, count_decimals,print +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.utils.utils import AttributeDict, ltp, zoneNY, trunc, count_decimals, print from v2realbot.utils.tlog import tlog from v2realbot.enums.enums import FillCondition from datetime import datetime, timedelta @@ -194,7 +194,7 @@ class Backtester: #TEST zkusime to nemazat, jak ovlivni performance #Mazeme, jinak je to hruza #nechavame na konci trady, které muzeme potrebovat pro consekutivni pravidlo - del self.btdata[0:index_end-2-FILL_CONS_TRADES_REQUIRED] + del self.btdata[0:index_end-2-BT_FILL_CONS_TRADES_REQUIRED] #ic("after delete",len(self.btdata[0:index_end])) if changes: return 1 @@ -235,9 +235,9 @@ class Backtester: #NASTVENI PODMINEK PLNENI fast_fill_condition = i[1] <= o.limit_price slow_fill_condition = i[1] < o.limit_price - if FILL_CONDITION_BUY_LIMIT == FillCondition.FAST: + if BT_FILL_CONDITION_BUY_LIMIT == FillCondition.FAST: fill_condition = fast_fill_condition - elif FILL_CONDITION_BUY_LIMIT == FillCondition.SLOW: + elif BT_FILL_CONDITION_BUY_LIMIT == FillCondition.SLOW: fill_condition = slow_fill_condition else: print("unknow fill condition") @@ -245,17 +245,17 @@ class Backtester: if float(i[0]) > float(order_min_fill_time+BT_DELAYS.limit_order_offset) and fill_condition: consec_cnt += 1 - if consec_cnt == FILL_CONS_TRADES_REQUIRED: + if consec_cnt == BT_FILL_CONS_TRADES_REQUIRED: #(1679081919.381649, 27.88) ic(i) fill_time = i[0] fill_price = i[1] print("FILL LIMIT BUY at", fill_time, datetime.fromtimestamp(fill_time).astimezone(zoneNY), "at",i[1]) - if FILL_LOG_SURROUNDING_TRADES != 0: + if BT_FILL_LOG_SURROUNDING_TRADES != 0: #TODO loguru - print("FILL SURR TRADES: before",work_range[index-FILL_LOG_SURROUNDING_TRADES:index]) - print("FILL SURR TRADES: fill and after",work_range[index:index+FILL_LOG_SURROUNDING_TRADES]) + print("FILL SURR TRADES: before",work_range[index-BT_FILL_LOG_SURROUNDING_TRADES:index]) + print("FILL SURR TRADES: fill and after",work_range[index:index+BT_FILL_LOG_SURROUNDING_TRADES]) break else: consec_cnt = 0 @@ -266,9 +266,9 @@ class Backtester: #NASTVENI PODMINEK PLNENI fast_fill_condition = i[1] >= o.limit_price slow_fill_condition = i[1] > o.limit_price - if FILL_CONDITION_SELL_LIMIT == FillCondition.FAST: + if BT_FILL_CONDITION_SELL_LIMIT == FillCondition.FAST: fill_condition = fast_fill_condition - elif FILL_CONDITION_SELL_LIMIT == FillCondition.SLOW: + elif BT_FILL_CONDITION_SELL_LIMIT == FillCondition.SLOW: fill_condition = slow_fill_condition else: print("unknown fill condition") @@ -276,16 +276,16 @@ class Backtester: if float(i[0]) > float(order_min_fill_time+BT_DELAYS.limit_order_offset) and fill_condition: consec_cnt += 1 - if consec_cnt == FILL_CONS_TRADES_REQUIRED: + if consec_cnt == BT_FILL_CONS_TRADES_REQUIRED: #(1679081919.381649, 27.88) ic(i) fill_time = i[0] fill_price = i[1] print("FILL LIMIT SELL at", fill_time, datetime.fromtimestamp(fill_time).astimezone(zoneNY), "at",i[1]) - if FILL_LOG_SURROUNDING_TRADES != 0: + if BT_FILL_LOG_SURROUNDING_TRADES != 0: #TODO loguru - print("FILL SELL SURR TRADES: before",work_range[index-FILL_LOG_SURROUNDING_TRADES:index]) - print("FILL SELL SURR TRADES: fill and after",work_range[index:index+FILL_LOG_SURROUNDING_TRADES]) + print("FILL SELL SURR TRADES: before",work_range[index-BT_FILL_LOG_SURROUNDING_TRADES:index]) + print("FILL SELL SURR TRADES: fill and after",work_range[index:index+BT_FILL_LOG_SURROUNDING_TRADES]) break else: consec_cnt = 0 diff --git a/v2realbot/config.py b/v2realbot/config.py index 514b929..6c44083 100644 --- a/v2realbot/config.py +++ b/v2realbot/config.py @@ -3,21 +3,21 @@ from v2realbot.enums.enums import Mode, Account, FillCondition from appdirs import user_data_dir -#how many consecutive trades with the fill price are necessary for LIMIT fill to happen() +#how many consecutive trades with the fill price are necessary for LIMIT fill to happen in backtesting #0 - optimistic, every knot high will fill the order #N - N consecutive trades required #not impl.yet #minimum is 1 -FILL_CONS_TRADES_REQUIRED = 2 -#during trade execution logs X-surrounding trades of the one that triggers the fill -FILL_LOG_SURROUNDING_TRADES = 10 -#fill condition for limit order +BT_FILL_CONS_TRADES_REQUIRED = 2 +#during bt trade execution logs X-surrounding trades of the one that triggers the fill +BT_FILL_LOG_SURROUNDING_TRADES = 10 +#fill condition for limit order in bt # fast - price has to be equal or bigger <= # slow - price has to be bigger < -FILL_CONDITION_BUY_LIMIT = FillCondition.FAST -FILL_CONDITION_SELL_LIMIT = FillCondition.FAST +BT_FILL_CONDITION_BUY_LIMIT = FillCondition.FAST +BT_FILL_CONDITION_SELL_LIMIT = FillCondition.FAST #no print in console -QUIET_MODE = False +QUIET_MODE = True #backend counter of api requests COUNT_API_REQUESTS = False #stratvars that cannot be changed in gui diff --git a/v2realbot/controller/services.py b/v2realbot/controller/services.py index 0b26b50..8e48204 100644 --- a/v2realbot/controller/services.py +++ b/v2realbot/controller/services.py @@ -222,12 +222,13 @@ def save_history(id: UUID, st: object, runner: Runner, reason: str = None): #zkousime precist profit z objektu try: profit = st.state.profit + trade_count = len(st.tradeList) except Exception as e: profit = str(e) for i in db.stratins: if str(i.id) == str(id): - i.history += "START:"+str(runner.run_started)+"STOP:"+str(runner.run_stopped)+"ACC:"+runner.run_account.value+"M:"+runner.run_mode.value+"PROFIT:"+str(profit)+ "REASON:" + str(reason) + i.history += "START:"+str(runner.run_started)+"STOP:"+str(runner.run_stopped)+"ACC:"+runner.run_account.value+"M:"+runner.run_mode.value+"PROFIT:"+str(round(profit,2))+ "TradeCNT:"+str(trade_count) + "REASON:" + str(reason) #i.history += str(runner.__dict__)+"
" db.save() diff --git a/v2realbot/enums/__pycache__/enums.cpython-310.pyc b/v2realbot/enums/__pycache__/enums.cpython-310.pyc index 4d9c1db..017f62b 100644 Binary files a/v2realbot/enums/__pycache__/enums.cpython-310.pyc and b/v2realbot/enums/__pycache__/enums.cpython-310.pyc differ diff --git a/v2realbot/static/index.html b/v2realbot/static/index.html index c7492bf..4ee058a 100644 --- a/v2realbot/static/index.html +++ b/v2realbot/static/index.html @@ -12,6 +12,7 @@ +
diff --git a/v2realbot/static/js/mytables.js b/v2realbot/static/js/mytables.js index 0f4818b..e594718 100644 --- a/v2realbot/static/js/mytables.js +++ b/v2realbot/static/js/mytables.js @@ -1,6 +1,35 @@ API_KEY = localStorage.getItem("api-key") +//KEY shortcuts +Mousetrap.bind('e', function() { + $( "#button_edit" ).trigger( "click" ); +}); +Mousetrap.bind('a', function() { + $( "#button_add" ).trigger( "click" ); +}); +Mousetrap.bind('d', function() { + $( "#button_dup" ).trigger( "click" ); +}); +Mousetrap.bind('c', function() { + $( "#button_copy" ).trigger( "click" ); +}); +Mousetrap.bind('r', function() { + $( "#button_run" ).trigger( "click" ); +}); +Mousetrap.bind('p', function() { + $( "#button_pause" ).trigger( "click" ); +}); +Mousetrap.bind('s', function() { + $( "#button_stop" ).trigger( "click" ); +}); +Mousetrap.bind('j', function() { + $( "#button_add_json" ).trigger( "click" ); +}); +Mousetrap.bind('x', function() { + $( "#button_delete" ).trigger( "click" ); +}); + //on button function store_api_key(event) { key = document.getElementById("api-key").value; diff --git a/v2realbot/static/js/mywebsocket.js b/v2realbot/static/js/mywebsocket.js index 555f349..3759572 100644 --- a/v2realbot/static/js/mywebsocket.js +++ b/v2realbot/static/js/mywebsocket.js @@ -62,7 +62,7 @@ function connect(event) { var lines = document.getElementById('lines') var line = document.createElement('div') line.classList.add("line") - const newLine = document.createTextNode("-----------------NEXT ITER------------------") + const newLine = document.createTextNode("---------------") line.appendChild(newLine) lines.appendChild(line) @@ -84,8 +84,7 @@ function connect(event) { logcnt++; row = '
'+logLine.time + " " + logLine.event + ' - '+ logLine.message+'
' str_row = JSON.stringify(logLine.details, null, 2) - - row_detail = '
' + str_row + '
' + row_detail = '
' + str_row + '
' var lines = document.getElementById('lines') var line = document.createElement('div') diff --git a/v2realbot/strategy/StrategyOrderLimitVykladaci.py b/v2realbot/strategy/StrategyOrderLimitVykladaci.py index 4d0d3aa..ccb8571 100644 --- a/v2realbot/strategy/StrategyOrderLimitVykladaci.py +++ b/v2realbot/strategy/StrategyOrderLimitVykladaci.py @@ -1,10 +1,11 @@ from v2realbot.strategy.base import Strategy -from v2realbot.utils.utils import parse_alpaca_timestamp, ltp, AttributeDict,trunc,price2dec, zoneNY, print +from v2realbot.utils.utils import parse_alpaca_timestamp, ltp, AttributeDict,trunc,price2dec, zoneNY, print, json_serial from v2realbot.utils.tlog import tlog, tlog_exception from v2realbot.enums.enums import Mode, Order, Account from alpaca.trading.models import TradeUpdate from alpaca.trading.enums import TradeEvent, OrderStatus from v2realbot.indicators.indicators import ema +import json #from rich import print from random import randrange from alpaca.common.exceptions import APIError @@ -12,13 +13,15 @@ import copy from threading import Event + class StrategyOrderLimitVykladaci(Strategy): def __init__(self, name: str, symbol: str, next: callable, init: callable, account: Account, mode: Mode = Mode.PAPER, stratvars: AttributeDict = None, open_rush: int = 30, close_rush: int = 30, pe: Event = None, se: Event = None) -> None: super().__init__(name, symbol, next, init, account, mode, stratvars, open_rush, close_rush, pe, se) async def orderUpdateBuy(self, data: TradeUpdate): o: Order = data.order - self.state.ilog(e="Příchozí BUY notifikace", msg="order status:"+o.status, status=o.status, orderid=str(o.id)) + ##nejak to vymyslet, aby se dal poslat cely Trade a serializoval se + self.state.ilog(e="Příchozí BUY notifikace", msg="order status:"+o.status, trade=json.loads(json.dumps(data, default=json_serial))) if o.status == OrderStatus.FILLED or o.status == OrderStatus.CANCELED: #pokud existuje objednavka v pendingbuys - vyhodime ji @@ -68,6 +71,7 @@ class StrategyOrderLimitVykladaci(Strategy): async def orderUpdateSell(self, data: TradeUpdate): + self.state.ilog(e="Příchozí SELL notifikace", msg="order status:"+data.order.status, trade=json.loads(json.dumps(data, default=json_serial))) #PROFIT #profit pocitame z TradeUpdate.price a TradeUpdate.qty - aktualne provedene mnozstvi a cena #naklady vypocteme z prumerne ceny, kterou mame v pozicich @@ -117,19 +121,19 @@ class StrategyOrderLimitVykladaci(Strategy): def buy(self, size = None, repeat: bool = False): print("overriden method to size&check maximum ") if int(self.state.positions) >= self.state.vars.maxpozic: - self.state.ilog(e="buy Maxim mnozstvi naplneno", curr_positions=self.state.positions) + self.state.ilog(e="buy Maxim mnozstvi naplneno", positions=self.state.positions) print("max mnostvi naplneno") return 0 if size is None: sizer = self.state.vars.chunk else: sizer = size + self.state.blockbuy = 1 self.state.vars.lastbuyindex = self.state.bars['index'][-1] - ic(self.state.blockbuy) - ic(self.state.vars.lastbuyindex) + self.state.ilog(e="send MARKET buy to if", msg="S:"+str(size), ltp=self.state.interface.get_last_price(self.state.symbol)) return self.state.interface.buy(size=sizer) - + def buy_l(self, price: float = None, size = None, repeat: bool = False): print("entering overriden BUY") if int(self.state.positions) >= self.state.vars.maxpozic: @@ -139,7 +143,7 @@ class StrategyOrderLimitVykladaci(Strategy): if price is None: price=price2dec((self.state.interface.get_last_price(self.symbol))) ic(price) print("odesilame LIMIT s cenou/qty", price, size) - self.state.ilog(e="send buy to if", msg="S:"+str(size)+" P:"+str(price), price=price, size=size) + self.state.ilog(e="send LIMIT buy to if", msg="S:"+str(size)+" P:"+str(price), price=price, size=size) order = self.state.interface.buy_l(price=price, size=size) print("ukladame pendingbuys") self.state.vars.pendingbuys[str(order)]=price diff --git a/v2realbot/strategy/__pycache__/StrategyOrderLimitVykladaci.cpython-310.pyc b/v2realbot/strategy/__pycache__/StrategyOrderLimitVykladaci.cpython-310.pyc index 778a6e7..02f26d6 100644 Binary files a/v2realbot/strategy/__pycache__/StrategyOrderLimitVykladaci.cpython-310.pyc and b/v2realbot/strategy/__pycache__/StrategyOrderLimitVykladaci.cpython-310.pyc differ diff --git a/v2realbot/strategy/__pycache__/base.cpython-310.pyc b/v2realbot/strategy/__pycache__/base.cpython-310.pyc index f23230f..cb2b7c8 100644 Binary files a/v2realbot/strategy/__pycache__/base.cpython-310.pyc and b/v2realbot/strategy/__pycache__/base.cpython-310.pyc differ diff --git a/v2realbot/strategy/base.py b/v2realbot/strategy/base.py index 534e8ae..8c7c2bc 100644 --- a/v2realbot/strategy/base.py +++ b/v2realbot/strategy/base.py @@ -17,6 +17,8 @@ from v2realbot.interfaces.backtest_interface import BacktestInterface from v2realbot.interfaces.live_interface import LiveInterface from alpaca.trading.enums import OrderSide from v2realbot.backtesting.backtester import Backtester +from alpaca.trading.models import TradeUpdate +from alpaca.trading.enums import TradeEvent, OrderStatus from threading import Event, current_thread import json @@ -48,6 +50,7 @@ class Strategy: self.account = account self.key = get_key(mode=self.mode, account=self.account) self.rtqueue = None + self.tradeList = [] #TODO predelat na dynamické queues @@ -308,7 +311,7 @@ class Strategy: #for order updates from LIVE or BACKTEST #updates are sent only for SYMBOL of strategy - async def order_updates(self, data): + async def order_updates(self, data: TradeUpdate): if self.mode == Mode.LIVE or self.mode == Mode.PAPER: now = datetime.now().timestamp() else: @@ -316,10 +319,16 @@ class Strategy: print("NOTIFICATION ARRIVED AT:", now) + #pokud jde o FILL zapisujeme do self.trades a notifikujeme + if data.event == TradeEvent.FILL: + self.tradeList.append(data) + ##TradeUpdate objekt better? order: Order = data.order - if order.side == OrderSide.BUY: await self.orderUpdateBuy(data) - if order.side == OrderSide.SELL: await self.orderUpdateSell(data) + if order.side == OrderSide.BUY: + await self.orderUpdateBuy(data) + if order.side == OrderSide.SELL: + await self.orderUpdateSell(data) async def orderUpdateBuy(self, data): print(data) diff --git a/v2realbot/utils/__pycache__/utils.cpython-310.pyc b/v2realbot/utils/__pycache__/utils.cpython-310.pyc index 8781759..b066da0 100644 Binary files a/v2realbot/utils/__pycache__/utils.cpython-310.pyc and b/v2realbot/utils/__pycache__/utils.cpython-310.pyc differ diff --git a/v2realbot/utils/utils.py b/v2realbot/utils/utils.py index bae320d..718163c 100644 --- a/v2realbot/utils/utils.py +++ b/v2realbot/utils/utils.py @@ -16,6 +16,24 @@ import tomli from v2realbot.config import DATA_DIR, QUIET_MODE import requests from uuid import UUID +from enum import Enum +#from v2realbot.enums.enums import Order +from v2realbot.common.model import Order, TradeUpdate + +def safe_get(collection, key, default=None): + """Get values from a collection without raising errors""" + + try: + return collection.get(key, default) + except TypeError: + pass + + try: + return collection[key] + except (IndexError, TypeError): + pass + + return default def send_to_telegram(message): apiToken = '5836666362:AAGPuzwp03tczMQTwTBiHW6VsZZ-1RCMAEE' @@ -38,6 +56,12 @@ def json_serial(obj): return obj.timestamp() if isinstance(obj, UUID): return str(obj) + if isinstance(obj, Enum): + return str(obj) + if type(obj) is Order: + return obj.__dict__ + if type(obj) is TradeUpdate: + return obj.__dict__ raise TypeError (str(obj)+"Type %s not serializable" % type(obj)) def parse_toml_string(tomlst: str):