strategy trade counter, gui shortcuts, better log

This commit is contained in:
David Brazda
2023-04-23 17:43:27 +02:00
parent a6e0ac987a
commit 83a7bb77da
16 changed files with 112 additions and 39 deletions

View File

@ -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,6 +130,11 @@ 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
##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):

View File

@ -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, FILL_CONDITION_BUY_LIMIT, FILL_CONDITION_SELL_LIMIT, FILL_LOG_SURROUNDING_TRADES, 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
from v2realbot.utils.utils import AttributeDict, ltp, zoneNY, trunc, count_decimals, print
from v2realbot.utils.tlog import tlog
from v2realbot.enums.enums import FillCondition
@ -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

View File

@ -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

View File

@ -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__)+"<BR>"
db.save()

View File

@ -12,6 +12,7 @@
<link href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css" rel="stylesheet">
<script src="/static/js/jquery.dataTables.min.js"></script>
<link rel="stylesheet" href="/static/main.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/mousetrap/1.4.6/mousetrap.min.js"></script>
</head>
<body>
<div id="main" class="mainConteiner flex-container">

View File

@ -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;

View File

@ -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 = '<div data-toggle="collapse" data-target="#rec'+logcnt+'">'+logLine.time + " " + logLine.event + ' - '+ logLine.message+'</div>'
str_row = JSON.stringify(logLine.details, null, 2)
row_detail = '<div id="rec'+logcnt+'" class="collapse pidi"><pre>' + str_row + '</pre></div>'
row_detail = '<div id="rec'+logcnt+'" data-toggle="collapse" data-target="#rec'+logcnt+'"class="collapse pidi"><pre>' + str_row + '</pre></div>'
var lines = document.getElementById('lines')
var line = document.createElement('div')

View File

@ -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,17 +121,17 @@ 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):
@ -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

View File

@ -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)

View File

@ -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):