first commit
This commit is contained in:
90
v2realbot/ENTRY_backtest_strategy.py
Normal file
90
v2realbot/ENTRY_backtest_strategy.py
Normal file
@ -0,0 +1,90 @@
|
||||
from strategy.base import Strategy, StrategyState
|
||||
from strategy.strategyOrderLimit import StrategyOrderLimit
|
||||
from enums import RecordType, StartBarAlign, Mode
|
||||
from config import API_KEY, SECRET_KEY, MAX_BATCH_SIZE, PAPER
|
||||
from indicators import ema
|
||||
from rich import print
|
||||
from utils.utils import ltp, isrising, isfalling,trunc,AttributeDict, zoneNY
|
||||
from datetime import datetime
|
||||
from icecream import install, ic
|
||||
install()
|
||||
ic.configureOutput(includeContext=True)
|
||||
#ic.disable()
|
||||
""""
|
||||
Simple strategie pro test backtesting
|
||||
"""
|
||||
|
||||
def next(data, state: StrategyState):
|
||||
print(10*"*","NEXT START",10*"*")
|
||||
ic(state.avgp, state.positions)
|
||||
ic(state.vars.limitka)
|
||||
ic(state.vars.lastbuyindex)
|
||||
ic(data)
|
||||
#print("last trade price")
|
||||
#print(state.interface.get_last_price("BAC"))
|
||||
#print(state.vars.novaprom)
|
||||
#print("trades history", state.trades)
|
||||
#print("bar history", state.bars)
|
||||
#print("ltp", ltp.price["BAC"], ltp.time["BAC"])
|
||||
|
||||
#TODO indikátory ukládat do vlastní historie - tu pak automaticky zobrazuje backtester graf
|
||||
|
||||
#TODO ema = state.indicators.ema a pouzivat nize ema, zjistit jestli bude fungovat
|
||||
|
||||
try:
|
||||
state.indicators.ema = ema(state.bars.hlcc4, state.vars.MA) #state.bars.vwap
|
||||
#trochu prasarna, EMAcko trunc na 3 mista - kdyz se osvedci, tak udelat efektivne
|
||||
state.indicators.ema = [trunc(i,3) for i in state.indicators.ema]
|
||||
ic(state.vars.MA, state.vars.Trend, state.indicators.ema[-5:])
|
||||
except Exception as e:
|
||||
print("No data for MA yet", str(e))
|
||||
|
||||
print("is falling",isfalling(state.indicators.ema,state.vars.Trend))
|
||||
print("is rising",isrising(state.indicators.ema,state.vars.Trend))
|
||||
|
||||
#ZDE JSEM SKONCIL
|
||||
#nejprve zacit s BARy
|
||||
|
||||
#TODO vyzkoušet limit buy - vetsina z nakupu by se dala koupit o cent dva mene
|
||||
#proto dodělat LTP pro BT, neco jako get_last_price(self.state.time)
|
||||
|
||||
|
||||
##TODO vyzkouset hlidat si sell objednavku sam na zaklade tradu
|
||||
# v pripade ze to jde nahoru(is rising - nebo jiny indikator) tak neprodavat
|
||||
#vyuzit CBARy k tomuto .....
|
||||
#triggerovat buy treba po polovine CBARu, kdyz se cena bude rovnat nebo bude nizsi nez low
|
||||
#a hned na to (po potvrzeni) hlidat sell +0.01 nebo kdyz roste nechat rust.vyzkouset na LIVE
|
||||
|
||||
if isfalling(state.indicators.ema,state.vars.Trend) and data['index'] > state.vars.lastbuyindex+state.vars.Trend: #and state.blockbuy == 0
|
||||
print("BUY MARKET")
|
||||
ic(data['updated'])
|
||||
ic(state.time)
|
||||
state.buy_l()
|
||||
|
||||
|
||||
print(10*"*","NEXT STOP",10*"*")
|
||||
|
||||
def init(state: StrategyState):
|
||||
#place to declare new vars
|
||||
print("INIT v main",state.name)
|
||||
state.vars['novaprom'] = 4
|
||||
state.indicators['ema'] = []
|
||||
|
||||
def main():
|
||||
stratvars = AttributeDict(maxpozic = 2400, chunk = 400, MA = 6, Trend = 7, profit = 0.03, lastbuyindex=-6, pendingbuys={},limitka = None)
|
||||
|
||||
s = StrategyOrderLimit(name = "BackTEST", symbol = "KO", next=next, init=init, stratvars=stratvars, debug=False)
|
||||
s.set_mode(mode = Mode.BT,
|
||||
start = datetime(2023, 2, 23, 9, 30, 0, 0, tzinfo=zoneNY),
|
||||
end = datetime(2023, 2, 23, 16, 00, 0, 0, tzinfo=zoneNY),
|
||||
cash=100000)
|
||||
|
||||
#na sekundovem baru nezaokrouhlovat MAcko
|
||||
s.add_data(symbol="KO",rectype=RecordType.BAR,timeframe=30,filters=None,update_ltp=True,align=StartBarAlign.RANDOM,mintick=0)
|
||||
#s.add_data(symbol="C",rectype=RecordType.BAR,timeframe=1,filters=None,update_ltp=True,align=StartBarAlign.ROUND,mintick=0)
|
||||
|
||||
s.start()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
99
v2realbot/ENTRY_backtest_strategyKOKA-ok.py
Normal file
99
v2realbot/ENTRY_backtest_strategyKOKA-ok.py
Normal file
@ -0,0 +1,99 @@
|
||||
# import os,sys
|
||||
# sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from v2realbot.strategy.base import Strategy, StrategyState
|
||||
from v2realbot.strategy.StrategyOrderLimitKOKA import StrategyOrderLimitKOKA
|
||||
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode
|
||||
from v2realbot.indicators.indicators import ema
|
||||
from rich import print
|
||||
from v2realbot.utils.utils import ltp, isrising, isfalling,trunc,AttributeDict, zoneNY
|
||||
from datetime import datetime
|
||||
from icecream import install, ic
|
||||
import os
|
||||
install()
|
||||
ic.configureOutput(includeContext=True)
|
||||
#ic.disable()
|
||||
""""
|
||||
Simple strategie LIMIT buy a lIMIT SELL working ok
|
||||
|
||||
DOkupovaci strategie, nakupuje dalsi pozice až po dalším signálu.
|
||||
|
||||
POZOR nekontroluje se maximální pozice - tzn. nejvic se vycerpalo 290, ale prezila kazdy den.
|
||||
Dobrá defenzivní pri nastaveni
|
||||
30s maxpozic = 290,chunk = 10,MA = 6,Trend = 6,profit = 0.02,
|
||||
"""
|
||||
stratvars = AttributeDict(maxpozic = 250,
|
||||
chunk = 10,
|
||||
MA = 6,
|
||||
Trend = 6,
|
||||
profit = 0.02,
|
||||
lastbuyindex=-6,
|
||||
pendingbuys={},
|
||||
limitka = None)
|
||||
|
||||
def next(data, state: StrategyState):
|
||||
print(10*"*","NEXT START",10*"*")
|
||||
ic(state.avgp, state.positions)
|
||||
ic(state.vars.limitka)
|
||||
ic(state.vars.lastbuyindex)
|
||||
ic(data)
|
||||
#print("last trade price")
|
||||
#print(state.interface.get_last_price("BAC"))
|
||||
#print(state.vars.novaprom)
|
||||
#print("trades history", state.trades)
|
||||
#print("bar history", state.bars)
|
||||
#print("ltp", ltp.price["BAC"], ltp.time["BAC"])
|
||||
|
||||
#TODO indikátory ukládat do vlastní historie - tu pak automaticky zobrazuje backtester graf
|
||||
|
||||
#TODO ema = state.indicators.ema a pouzivat nize ema, zjistit jestli bude fungovat
|
||||
|
||||
try:
|
||||
state.indicators.ema = ema(state.bars.hlcc4, state.vars.MA) #state.bars.vwap
|
||||
#trochu prasarna, EMAcko trunc na 3 mista - kdyz se osvedci, tak udelat efektivne
|
||||
state.indicators.ema = [trunc(i,3) for i in state.indicators.ema]
|
||||
ic(state.vars.MA, state.vars.Trend, state.indicators.ema[-5:])
|
||||
except Exception as e:
|
||||
print("No data for MA yet", str(e))
|
||||
|
||||
print("is falling",isfalling(state.indicators.ema,state.vars.Trend))
|
||||
print("is rising",isrising(state.indicators.ema,state.vars.Trend))
|
||||
|
||||
#ZDE JSEM SKONCIL
|
||||
#nejprve zacit s BARy
|
||||
|
||||
#TODO vyzkoušet limit buy - vetsina z nakupu by se dala koupit o cent dva mene
|
||||
#proto dodělat LTP pro BT, neco jako get_last_price(self.state.time)
|
||||
if isfalling(state.indicators.ema,state.vars.Trend) and data['index'] > state.vars.lastbuyindex+state.vars.Trend: #and state.blockbuy == 0
|
||||
print("BUY MARKET")
|
||||
ic(data['updated'])
|
||||
ic(state.time)
|
||||
state.buy_l()
|
||||
|
||||
|
||||
print(10*"*","NEXT STOP",10*"*")
|
||||
|
||||
def init(state: StrategyState):
|
||||
#place to declare new vars
|
||||
print("INIT v main",state.name)
|
||||
state.vars['novaprom'] = 4
|
||||
state.indicators['ema'] = []
|
||||
|
||||
def main():
|
||||
name = os.path.basename(__file__)
|
||||
s = StrategyOrderLimitKOKA(name = name, symbol = "BAC", next=next, init=init, stratvars=stratvars, open_rush=30, close_rush=0)
|
||||
s.set_mode(mode = Mode.PAPER,
|
||||
debug = False,
|
||||
start = datetime(2023, 3, 6, 9, 30, 0, 0, tzinfo=zoneNY),
|
||||
end = datetime(2023, 3, 9, 16, 0, 0, 0, tzinfo=zoneNY),
|
||||
cash=100000)
|
||||
|
||||
#na sekundovem baru nezaokrouhlovat MAcko
|
||||
s.add_data(symbol="BAC",rectype=RecordType.BAR,timeframe=30,filters=None,update_ltp=True,align=StartBarAlign.ROUND,mintick=0)
|
||||
#s.add_data(symbol="C",rectype=RecordType.BAR,timeframe=1,filters=None,update_ltp=True,align=StartBarAlign.ROUND,mintick=0)
|
||||
|
||||
s.start()
|
||||
print("zastavujeme")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
243
v2realbot/ENTRY_backtest_strategyVykladaci.py
Normal file
243
v2realbot/ENTRY_backtest_strategyVykladaci.py
Normal file
@ -0,0 +1,243 @@
|
||||
import os,sys
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from v2realbot.strategy.base import StrategyState
|
||||
from v2realbot.strategy.StrategyOrderLimitVykladaci import StrategyOrderLimitVykladaci
|
||||
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account
|
||||
from v2realbot.indicators.indicators import ema
|
||||
from v2realbot.utils.utils import ltp, isrising, isfalling,trunc,AttributeDict, zoneNY, price2dec, dict_replace_value
|
||||
from datetime import datetime
|
||||
from icecream import install, ic
|
||||
from rich import print
|
||||
from threading import Event
|
||||
import asyncio
|
||||
import os
|
||||
import tomli
|
||||
install()
|
||||
ic.configureOutput(includeContext=True)
|
||||
#ic.disable()
|
||||
|
||||
print(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
""""
|
||||
Vykladaci strategie refactored z původního engine
|
||||
Params:
|
||||
(maxpozic = 200, chunk = 50, MA = 6, Trend = 6, profit = 0.02, lastbuyindex=-6, pendingbuys={},limitka = None, jevylozeno=0, ticks2reset = 0.04, blockbuy=0)
|
||||
|
||||
|
||||
Více nakupuje oproti Dokupovaci. Tady vylozime a nakupujeme 5 pozic hned. Pri dokupovaci se dokupuje, az na zaklade dalsich triggeru.
|
||||
Do budoucna vice ridit nakupy pri klesani - napr. vyložení jen 2-3 pozic a další dokupy až po triggeru.
|
||||
#
|
||||
"""
|
||||
stratvars = AttributeDict(maxpozic = 250,
|
||||
chunk = 10,
|
||||
MA = 3,
|
||||
Trend = 3,
|
||||
profit = 0.02,
|
||||
lastbuyindex=-6,
|
||||
pendingbuys={},
|
||||
limitka = None,
|
||||
jevylozeno=0,
|
||||
vykladka=5,
|
||||
curve = [0.01, 0.01, 0.01, 0, 0.02, 0.02, 0.01,0.01, 0.01,0.03, 0.01, 0.01, 0.01,0.04, 0.01,0.01, 0.01,0.05, 0.01,0.01, 0.01,0.01, 0.06,0.01, 0.01,0.01, 0.01],
|
||||
blockbuy = 0,
|
||||
ticks2reset = 0.04)
|
||||
##toto rozparsovat a strategii spustit stejne jako v main
|
||||
toml_string = """
|
||||
[[strategies]]
|
||||
name = "V1 na BAC"
|
||||
symbol = "BAC"
|
||||
script = "ENTRY_backtest_strategyVykladaci"
|
||||
class = "StrategyOrderLimitVykladaci"
|
||||
open_rush = 0
|
||||
close_rush = 0
|
||||
[strategies.stratvars]
|
||||
maxpozic = 200
|
||||
chunk = 10
|
||||
MA = 6
|
||||
Trend = 5
|
||||
profit = 0.02
|
||||
lastbuyindex=-6
|
||||
pendingbuys={}
|
||||
limitka = "None"
|
||||
jevylozeno=0
|
||||
vykladka=5
|
||||
curve = [0.01, 0.01, 0.01,0.01, 0.02, 0.01,0.01, 0.01,0.03, 0.01, 0.01, 0.01,0.04, 0.01,0.01, 0.01,0.05, 0.01,0.01, 0.01,0.01, 0.06,0.01, 0.01,0.01, 0.01]
|
||||
blockbuy = 0
|
||||
ticks2reset = 0.04
|
||||
[[strategies.add_data]]
|
||||
symbol="BAC"
|
||||
rectype="bar"
|
||||
timeframe=5
|
||||
update_ltp=true
|
||||
align="round"
|
||||
mintick=0
|
||||
minsize=100
|
||||
exthours=false
|
||||
"""
|
||||
|
||||
def next(data, state: StrategyState):
|
||||
print(10*"*","NEXT START",10*"*")
|
||||
ic(state.avgp, state.positions)
|
||||
ic(state.vars)
|
||||
ic(data)
|
||||
|
||||
#mozna presunout o level vys
|
||||
def vyloz():
|
||||
##prvni se vyklada na aktualni cenu, další jdou podle krivky, nula v krivce zvyšuje množství pro následující iteraci
|
||||
#curve = [0.01, 0.01, 0, 0, 0.01, 0, 0, 0, 0.02, 0, 0, 0, 0.03, 0,0,0,0,0, 0.02, 0,0,0,0,0,0, 0.02]
|
||||
curve = state.vars.curve
|
||||
|
||||
#vykladani po 5ti kusech, když zbývají 2 a méně, tak děláme nový výklad
|
||||
vykladka = state.vars.vykladka
|
||||
#kolik muzu max vylozit
|
||||
kolikmuzu = int((int(state.vars.maxpozic) - int(state.positions))/int(state.vars.chunk))
|
||||
if kolikmuzu < vykladka: vykladka = kolikmuzu
|
||||
|
||||
if len(curve) < vykladka:
|
||||
vykladka = len(curve)
|
||||
qty = int(state.vars.chunk)
|
||||
last_price = price2dec(state.interface.get_last_price(state.symbol))
|
||||
#profit = float(state.vars.profit)
|
||||
price = last_price
|
||||
##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)
|
||||
print("prvni limitka na aktuální cenu. Další podle křicvky", price, qty)
|
||||
for i in range(0,vykladka-1):
|
||||
price = price2dec(float(price - curve[i]))
|
||||
if price == last_price:
|
||||
qty = qty + int(state.vars.chunk)
|
||||
else:
|
||||
state.buy_l(price=price, size=qty)
|
||||
print(i,"BUY limitka - delta",curve[i]," cena:", price, "mnozstvi:", qty)
|
||||
qty = int(state.vars.chunk)
|
||||
last_price = price
|
||||
state.vars.blockbuy = 1
|
||||
state.vars.jevylozeno = 1
|
||||
|
||||
#CBAR protection, only 1x order per CBAR - then wait until another confirmed bar
|
||||
|
||||
if state.vars.blockbuy == 1 and state.rectype == RecordType.CBAR:
|
||||
if state.bars.confirmed[-1] == 0:
|
||||
print("OCHR: multibuy protection. waiting for next bar")
|
||||
return 0
|
||||
# pop potvrzeni jeste jednou vratime (aby se nekoupilo znova, je stale ten stejny bar)
|
||||
# a pak dalsi vejde az po minticku
|
||||
else:
|
||||
# pro vykladaci
|
||||
state.vars.blockbuy = 0
|
||||
return 0
|
||||
|
||||
try:
|
||||
print(state.vars.MA, "MACKO")
|
||||
print(state.bars.hlcc4)
|
||||
state.indicators.ema = ema(state.bars.hlcc4, state.vars.MA) #state.bars.vwap
|
||||
#trochu prasarna, EMAcko trunc na 3 mista - kdyz se osvedci, tak udelat efektivne
|
||||
state.indicators.ema = [trunc(i,3) for i in state.indicators.ema]
|
||||
ic(state.vars.MA, state.vars.Trend, state.indicators.ema[-5:])
|
||||
except Exception as e:
|
||||
print("No data for MA yet", str(e))
|
||||
|
||||
print("is falling",isfalling(state.indicators.ema,state.vars.Trend))
|
||||
print("is rising",isrising(state.indicators.ema,state.vars.Trend))
|
||||
|
||||
#and data['index'] > state.vars.lastbuyindex+state.vars.Trend:
|
||||
#neni vylozeno muzeme nakupovat
|
||||
if ic(state.vars.jevylozeno) == 0:
|
||||
print("Neni vylozeno, muzeme testovat nakup")
|
||||
|
||||
if isfalling(state.indicators.ema,state.vars.Trend):
|
||||
print("BUY MARKET")
|
||||
ic(data['updated'])
|
||||
ic(state.time)
|
||||
#zatim vykladame full
|
||||
#positions = int(int(state.vars.maxpozic)/int(state.vars.chunk))
|
||||
vyloz()
|
||||
|
||||
## testuje aktualni cenu od nejvyssi visici limitky
|
||||
##toto spoustet jednou za X iterací - ted to jede pokazdé
|
||||
#pokud to ujede o vic, rusime limitky
|
||||
#TODO: zvazit jestli nechat i pri otevrenych pozicich, zatim nechavame
|
||||
#TODO int(int(state.oa.poz)/int(state.variables.chunk)) > X
|
||||
|
||||
#TODO predelat mechanismus ticků (zrelativizovat), aby byl pouzitelny na tituly s ruznou cenou
|
||||
#TODO spoustet 1x za X iteraci nebo cas
|
||||
if state.vars.jevylozeno == 1:
|
||||
#pokud mame vylozeno a cena je vetsi nez 0.04
|
||||
if len(state.vars.pendingbuys)>0:
|
||||
maxprice = max(state.vars.pendingbuys.values())
|
||||
print("max cena v orderbuys", maxprice)
|
||||
if state.interface.get_last_price(state.symbol) > float(maxprice) + state.vars.ticks2reset:
|
||||
print("ujelo to vice nez o " + str(state.vars.ticks2reset) + ", rusime limit buye")
|
||||
##TODO toto nejak vymyslet - duplikovat?
|
||||
res = asyncio.run(state.cancel_pending_buys())
|
||||
|
||||
|
||||
#pokud mame vylozeno a pendingbuys se vyklepou a
|
||||
# 1 vykladame idned znovu
|
||||
# vyloz()
|
||||
# 2 nebo - počkat zase na signál a pokračovat dál
|
||||
# state.vars.blockbuy = 0
|
||||
# state.vars.jevylozeno = 0
|
||||
# 3 nebo - počkat na signál s enablovaným lastbuy indexem (tzn. počká nutně ještě pár barů)
|
||||
#podle BT vyhodnejsi vylozit ihned
|
||||
if len(state.vars.pendingbuys) == 0:
|
||||
state.vars.blockbuy = 0
|
||||
state.vars.jevylozeno = 0
|
||||
|
||||
#TODO toto dodelat
|
||||
#pokud je vylozeno a mame pozice a neexistuje limitka - pak ji vytvorim
|
||||
# if int(state.oe.poz)>0 and state.oe.limitka == 0:
|
||||
# #pro jistotu updatujeme pozice
|
||||
# state.oe.avgp, state.oe.poz = state.oe.pos()
|
||||
# if int(state.oe.poz) > 0:
|
||||
# cena = round(float(state.oe.avgp) + float(state.oe.stratvars["profit"]),2)
|
||||
# print("BUGF: limitka neni vytvarime, a to za cenu",cena,"mnozstvi",state.oe.poz)
|
||||
# print("aktuzalni ltp",ltp.price[state.oe.symbol])
|
||||
|
||||
# try:
|
||||
# state.oe.limitka = state.oe.sell_noasync(cena, state.oe.poz)
|
||||
# print("vytvorena limitka", state.oe.limitka)
|
||||
# except Exception as e:
|
||||
# print("Neslo vytvorit profitku. Problem,ale jedeme dal",str(e))
|
||||
# pass
|
||||
# ##raise Exception(e)
|
||||
|
||||
print(10*"*","NEXT STOP",10*"*")
|
||||
|
||||
def init(state: StrategyState):
|
||||
#place to declare new vars
|
||||
print("INIT v main",state.name)
|
||||
state.indicators['ema'] = []
|
||||
|
||||
def main():
|
||||
|
||||
try:
|
||||
strat_settings = tomli.loads("]] this is invalid TOML [[")
|
||||
except tomli.TOMLDecodeError:
|
||||
print("Yep, definitely not valid.")
|
||||
|
||||
#strat_settings = dict_replace_value(strat_settings, "None", None)
|
||||
|
||||
name = os.path.basename(__file__)
|
||||
se = Event()
|
||||
pe = Event()
|
||||
s = StrategyOrderLimitVykladaci(name = name, symbol = "BAC", account=Account.ACCOUNT2, next=next, init=init, stratvars=stratvars, open_rush=40, close_rush=0, pe=pe, se=se)
|
||||
s.set_mode(mode = Mode.PAPER,
|
||||
debug = False,
|
||||
start = datetime(2023, 3, 30, 9, 30, 0, 0, tzinfo=zoneNY),
|
||||
end = datetime(2023, 3, 31, 16, 0, 0, 0, tzinfo=zoneNY),
|
||||
cash=100000)
|
||||
|
||||
#na sekundovem baru nezaokrouhlovat MAcko
|
||||
s.add_data(symbol="BAC",rectype=RecordType.BAR,timeframe=5,minsize=100,update_ltp=True,align=StartBarAlign.ROUND,mintick=0, exthours=True)
|
||||
#s.add_data(symbol="C",rectype=RecordType.BAR,timeframe=1,filters=None,update_ltp=True,align=StartBarAlign.ROUND,mintick=0)
|
||||
|
||||
s.start()
|
||||
print("zastavujeme")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
103
v2realbot/ENTRY_backtest_strategyWatched.py
Normal file
103
v2realbot/ENTRY_backtest_strategyWatched.py
Normal file
@ -0,0 +1,103 @@
|
||||
from strategy.base import Strategy, StrategyState
|
||||
from strategy.strategyOrderLimitWatched import StrategyOrderLimitWatched
|
||||
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode
|
||||
from indicators import ema
|
||||
from rich import print
|
||||
from utils.utils import ltp, isrising, isfalling,trunc,AttributeDict, zoneNY
|
||||
from datetime import datetime
|
||||
from icecream import install, ic
|
||||
install()
|
||||
ic.configureOutput(includeContext=True)
|
||||
#ic.disable()
|
||||
""""
|
||||
Simple strategie pro test backtesting
|
||||
"""
|
||||
|
||||
def next(data, state: StrategyState):
|
||||
print(10*"*","NEXT START",10*"*")
|
||||
ic(state.avgp, state.positions)
|
||||
ic(state.vars.lastbuyindex)
|
||||
ic(data)
|
||||
ic(state.positions)
|
||||
ic(state.vars.watched)
|
||||
ic(state.vars.wait)
|
||||
|
||||
try:
|
||||
state.indicators.ema = ema(state.bars.hlcc4, state.vars.MA) #state.bars.vwap
|
||||
#trochu prasarna, EMAcko trunc na 3 mista - kdyz se osvedci, tak udelat efektivne
|
||||
state.indicators.ema = [trunc(i,3) for i in state.indicators.ema]
|
||||
ic(state.vars.MA, state.vars.Trend, state.indicators.ema[-5:])
|
||||
except Exception as e:
|
||||
print("No data for MA yet", str(e))
|
||||
|
||||
print("is falling",isfalling(state.indicators.ema,state.vars.Trend))
|
||||
print("is rising",isrising(state.indicators.ema,state.vars.Trend))
|
||||
|
||||
#ZDE JSEM SKONCIL
|
||||
#nejprve zacit s BARy
|
||||
|
||||
#TODO vyzkoušet limit buy - vetsina z nakupu by se dala koupit o cent dva mene
|
||||
#proto dodělat LTP pro BT, neco jako get_last_price(self.state.time)
|
||||
|
||||
|
||||
##TODO vyzkouset hlidat si sell objednavku sam na zaklade tradu
|
||||
# v pripade ze to jde nahoru(is rising - nebo jiny indikator) tak neprodavat
|
||||
#vyuzit CBARy k tomuto .....
|
||||
#triggerovat buy treba po polovine CBARu, kdyz se cena bude rovnat nebo bude nizsi nez low
|
||||
#a hned na to (po potvrzeni) hlidat sell +0.01 nebo kdyz roste nechat rust.vyzkouset na LIVE
|
||||
|
||||
datetime.fromtimestamp(state.last_trade_time)
|
||||
casbaru = datetime.fromtimestamp(state.last_trade_time)-data['time']
|
||||
kupuj = casbaru.seconds > int(int(data['resolution']) * 0.4)
|
||||
ic(kupuj)
|
||||
ic(casbaru.seconds)
|
||||
|
||||
#kupujeme kdyz v druhe polovine baru je aktualni cena=low (nejnizsi)
|
||||
#isrising(state.indicators.ema,state.vars.Trend)
|
||||
#kdyz se v jednom baru pohneme o 2
|
||||
if kupuj and data['confirmed'] != 1 and data['close'] == data['low'] and float(data['close']) + 0.01 < data['open'] and state.vars.wait is False and state.vars.watched is None:
|
||||
print("BUY MARKET")
|
||||
ic(data['updated'])
|
||||
ic(state.time)
|
||||
##updatneme realnou cenou po fillu
|
||||
state.buy()
|
||||
state.vars.wait = True
|
||||
|
||||
if state.vars.watched and state.vars.wait is False:
|
||||
currprice = state.interface.get_last_price(symbol = state.symbol)
|
||||
ic(currprice)
|
||||
if float(currprice) > (float(state.vars.watched) + float(state.vars.profit)):
|
||||
ic(state.time)
|
||||
ic("prodavame", currprice)
|
||||
print("PRODAVAME")
|
||||
##vymyslet jak odchytavat obecne chyby a vracet cislo objednavky
|
||||
state.interface.sell(size=1)
|
||||
state.vars.wait = True
|
||||
|
||||
|
||||
print(10*"*","NEXT STOP",10*"*")
|
||||
|
||||
def init(state: StrategyState):
|
||||
#place to declare new vars
|
||||
print("INIT v main",state.name)
|
||||
state.vars['novaprom'] = 4
|
||||
state.indicators['ema'] = []
|
||||
|
||||
def main():
|
||||
stratvars = AttributeDict(maxpozic = 1, chunk = 1, MA = 2, Trend = 2, profit = 0.005, lastbuyindex=-6, pendingbuys={},watched = None, wait = False)
|
||||
|
||||
s = StrategyOrderLimitWatched(name = "BackTEST", symbol = "BAC", next=next, init=init, stratvars=stratvars, debug=False)
|
||||
s.set_mode(mode = Mode.PAPER,
|
||||
start = datetime(2023, 3, 24, 11, 30, 0, 0, tzinfo=zoneNY),
|
||||
end = datetime(2023, 3, 24, 11, 45, 0, 0, tzinfo=zoneNY),
|
||||
cash=100000)
|
||||
|
||||
#na sekundovem baru nezaokrouhlovat MAcko
|
||||
s.add_data(symbol="BAC",rectype=RecordType.CBAR,timeframe=5,filters=None,update_ltp=True,align=StartBarAlign.ROUND,mintick=0)
|
||||
#s.add_data(symbol="C",rectype=RecordType.BAR,timeframe=1,filters=None,update_ltp=True,align=StartBarAlign.ROUND,mintick=0)
|
||||
|
||||
s.start()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
70
v2realbot/ENTRY_latency_roundtrip.py
Normal file
70
v2realbot/ENTRY_latency_roundtrip.py
Normal file
@ -0,0 +1,70 @@
|
||||
from strategy.base import Strategy
|
||||
from strategy.base import StrategyState
|
||||
from enums import RecordType, StartBarAlign, Mode
|
||||
from config import API_KEY, SECRET_KEY, MAX_BATCH_SIZE, PAPER
|
||||
from indicators import ema
|
||||
from rich import print
|
||||
from utils import ltp, isrising, isfalling,trunc,AttributeDict
|
||||
from datetime import datetime
|
||||
|
||||
""""
|
||||
Simple strategie pro měření roundtripu na konkrétním prostředí
|
||||
|
||||
- koupí 1 akcii a vypíše časy
|
||||
- tradu který triggeroval
|
||||
- čas triggeru buy
|
||||
- příchod zpětné notifikace NEW
|
||||
- příchod zpětné notifikace FILL
|
||||
- vypíše trade, když přijde do agregátoru (vyžaduje do agregátoru na řádek 79: if int(data['s']) == 1: print(data))
|
||||
- vyžaduje ve strategy base v orderupdate
|
||||
- print("NOTIFICATION ARRIVED AT:", datetime.now().timestamp(), datetime.now())
|
||||
- print(data)
|
||||
|
||||
výsledek latencyroudntrip.log
|
||||
|
||||
"""
|
||||
|
||||
def next(data, state: StrategyState):
|
||||
print("avgp:", state.avgp)
|
||||
print("positions", state.positions)
|
||||
print("přišly tyto data", data)
|
||||
print("bar updated time:", data['updated'], datetime.fromtimestamp(data['updated']))
|
||||
print("state time(now):", state.time, datetime.fromtimestamp(state.time))
|
||||
#print("trades history", state.trades)
|
||||
#print("bar history", state.bars)
|
||||
print("ltp", ltp.price["BAC"], ltp.time["BAC"])
|
||||
|
||||
try:
|
||||
ema_output = ema(state.bars.hlcc4, state.vars.MA) #state.bars.vwap
|
||||
ema_output = [trunc(i,3) for i in ema_output]
|
||||
print("emacko na wvap",state.vars.MA,":", ema_output[-5:])
|
||||
except:
|
||||
print("No data for MA yet")
|
||||
|
||||
print("MA is falling",state.vars.Trend,"value:",isfalling(ema_output,state.vars.Trend))
|
||||
print("MA is rising",state.vars.Trend,"value:",isrising(ema_output,state.vars.Trend))
|
||||
|
||||
if isfalling(ema_output,state.vars.Trend) and state.vars.blockbuy == 0:
|
||||
print("kupujeme MARKET")
|
||||
print("v baru mame cas posledniho tradu", data['updated'])
|
||||
print("na LIVE je skutecny cas - tento ", state.time)
|
||||
print("v nem odesilame")
|
||||
state.interface.buy(time=state.time)
|
||||
state.vars.blockbuy = 1
|
||||
|
||||
def init(state: StrategyState):
|
||||
print("INIT strategie", state.name, "symbol", state.symbol)
|
||||
|
||||
def main():
|
||||
stratvars = AttributeDict(maxpozic = 200, chunk = 10, MA = 3, Trend = 3, profit = 0.01, blockbuy=0, lastbuyindex=0, pendingbuys={})
|
||||
s = Strategy(name = "BackTEST", symbol = "BAC", next=next, init=init, stratvars=stratvars)
|
||||
|
||||
#s.set_mode(mode = Mode.BT, start= datetime(2023, 3, 16, 15, 54, 30, 0), end=datetime(2023, 3, 16, 15, 54, 40, 999999))
|
||||
|
||||
s.add_data(symbol="BAC",rectype=RecordType.BAR,timeframe=5,filters=None,update_ltp=True,align=StartBarAlign.ROUND,mintick=0)
|
||||
#s.add_data(symbol="C",rectype=RecordType.BAR,timeframe=1,filters=None,update_ltp=True,align=StartBarAlign.ROUND,mintick=0)
|
||||
|
||||
s.start()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
5
v2realbot/ENTRY_strategy_vykladaci_REFACTOR.py
Normal file
5
v2realbot/ENTRY_strategy_vykladaci_REFACTOR.py
Normal file
@ -0,0 +1,5 @@
|
||||
##pro refactor vykladaci strategie: tipy:
|
||||
|
||||
##pri CANCEL pendindbuys - lokalni pending buys zrušit hnedka po synchronním odeslání, nemusim cekat na potvrzeni
|
||||
##ve zpetne notifikaci FILLU - je uvedena aktuální počet pozice - tzn. nemusím volat POS, ale jen si je dotahnu odsud. Pozor avgp tu neni.
|
||||
|
||||
200
v2realbot/FINAL_MA_VYK_10s_AGGR_mastrategyvykladaci.py
Normal file
200
v2realbot/FINAL_MA_VYK_10s_AGGR_mastrategyvykladaci.py
Normal file
@ -0,0 +1,200 @@
|
||||
from strategy import MyStrategy, StrategyState, Strategy
|
||||
from enums import RecordType, StartBarAlign
|
||||
from config import API_KEY, SECRET_KEY, MAX_BATCH_SIZE, PAPER
|
||||
from indicators import ema
|
||||
from rich import print
|
||||
from utils import ltp, isrising, isfalling,trunc
|
||||
|
||||
|
||||
|
||||
""""
|
||||
TBD - zpomalit - nekupovat okamzite nechat dychat
|
||||
|
||||
|
||||
MA Vykládcí Strategie s LIMIT BUY
|
||||
# aktualni nastaveni - VELMI AGRESIVNI, STALE KUPUJE, IDEALNI NA POTVRZENE RUSTOVE DNY
|
||||
- jede na 10s
|
||||
- BUY and HOLD alternative
|
||||
- dat do seznamu hotovych strategii
|
||||
|
||||
atributy:
|
||||
ticks2reset - počet ticků po kterých se resetnou prikazy pokud neni plneni
|
||||
|
||||
TODO:
|
||||
- pridat reconciliaci po kazdem X tem potvrzenem baru - konzolidace otevrenych pozic a limitek
|
||||
- do kazde asynchronni notifkace orderupdate dat ochranu, kdyz uz ten stav neplati (tzn. napr. pro potvrzeni buye se uz prodalo)
|
||||
- podchytit: kdykoliv limitka neexistuje, ale mam pozice, tak ji vytvorit (podchytit situace v rush hours)
|
||||
- cancel pendign buys - dictionary changed size during iteration podychytit. lock
|
||||
"""
|
||||
ticks2reset = 0.03
|
||||
#TODO pokud bar klesne o jeden nebo vice - tak signál - DEFENZIVNI MOD
|
||||
#TODO pouzivat tu objekt ochrana (ktery jen vlozim do kodu a kdyz vrati exceptionu tak jdeme do dalsi iterace)
|
||||
# tak zustane strategie cista
|
||||
#TODO rušit (pending buys) když oe.poz = 0 a od nejvetsi pending buys je
|
||||
# ltp.price vice nez 5 ticků
|
||||
|
||||
def next(data, state: StrategyState):
|
||||
|
||||
def vyloz(pozic: int):
|
||||
print("vykladame na pocet pozic", pozic)
|
||||
# defenzivni krivka
|
||||
curve = [0.01, 0.01, 0.01, 0.02, 0.02, 0.02, 0.02,0.03,0.03,0.03,0.03, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04]
|
||||
#cent curve = [0.01, 0.01, 0.01,0.01, 0.01, 0.01,0.01, 0.01,0.01, 0.01, 0.01, 0.01,0.01, 0.01,0.01, 0.01,0.01, 0.01,0.01, 0.01,0.01, 0.01,0.01, 0.01,0.01, 0.01]
|
||||
#defenzivnejsi s vetsimi mezerami v druhe tretine a vetsi vahou pro dokupy
|
||||
|
||||
# krivka pro AVG, tzn. exponencialne pridavame 0.00
|
||||
curve = [0.01, 0, 0.01, 0, 0, 0.01, 0, 0, 0, 0.01, 0, 0, 0, 0, 0.01, 0, 0, 0, 0, 0, 0.01, 0,0,0,0,0,0, 0.01, 0,0,0,0,0,0,0,0,0.01,0,0,0,0,0,0,0,0,0, 0.01,0,0,0,0,0,0,0,0,0,0.01, 0,0,0,0,0,00,0,0,0, 0.5, 0,0,0,0,0.5,0,0,0]
|
||||
|
||||
# 10ty clen je o 0.05 tzn.triggeruje nam to tick2reset
|
||||
#curve = [0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.02, 0.02, 0.05, 0.01, 0.04, 0.01, 0.01, 0.04, 0.05, 0.03, 0.05, 0.01, 0.03, 0.01,0.01, 0.04, 0.01, 0.05,0.01, 0.01,0.01, 0.01]
|
||||
|
||||
#defenzivni krivku nastavime vetsimi mezerami a v nich 0.01 - tim se prida vetsi mnostvi a vic se naredi
|
||||
# 0.04, 0.01,
|
||||
|
||||
#curve = [0.01,0.01, 0.01, 0.02, 0.02, 0.02, 0.02]
|
||||
#cena pro prvni objednavky
|
||||
price = trunc(float(ltp.price[state.oe.symbol]),2)
|
||||
print("aktualni cena pri vykladu - pro prvni", price)
|
||||
qty = int(state.variables.chunk)
|
||||
last_price = price
|
||||
if len(curve) < pozic:
|
||||
pozic = len(curve)
|
||||
#stejné ceny posilame jako jednu objednávku
|
||||
for i in range(0,pozic):
|
||||
price = round(float(price - curve[i]),2)
|
||||
if price == last_price:
|
||||
qty = qty + int(state.variables.chunk)
|
||||
else:
|
||||
#flush last_price and stored qty
|
||||
# OPT: pokud bude kupovat prilis brzy, osvedcila se prvni cena -0.01 (tzn. stavi prehodit last_price za price)
|
||||
state.oe.buy_l(price=last_price, size=qty, force=1)
|
||||
print(i,"BUY limitka - delta",curve[i]," cena:", price, "mnozstvi:", qty)
|
||||
qty = int(state.variables.chunk)
|
||||
|
||||
last_price = price
|
||||
|
||||
#TODO pokud cena stejna jako predchozi, tak navys predchozi - abychom nemeli vice objednavek na stejne cene (zbytecne)
|
||||
|
||||
|
||||
|
||||
|
||||
print("pending buys", state.oe.stratvars['pendingbuys'])
|
||||
print("je vylozeno",state.oe.stratvars['jevylozeno'])
|
||||
print("avg,poz,limitka",state.oe.avgp, state.oe.poz, state.oe.limitka)
|
||||
print("last buy price", state.oe.lastbuy)
|
||||
#CBAR protection, only 1x order per CBAR - then wait until another confirmed bar
|
||||
if state.variables.blockbuy == 1:
|
||||
if state.bars.confirmed[-1] == 0:
|
||||
print("OCHR: multibuy protection. waiting for next bar")
|
||||
return 0
|
||||
# pop potvrzeni jeste jednou vratime (aby se nekoupilo znova, je stale ten stejny bar)
|
||||
# a pak dalsi vejde az po minticku
|
||||
else:
|
||||
# pro vykladaci
|
||||
state.variables.blockbuy = 0
|
||||
return 0
|
||||
|
||||
|
||||
#print(state.bars) .
|
||||
# print("next")
|
||||
# print(data)
|
||||
|
||||
#TODO zkusit hlcc4
|
||||
try:
|
||||
ema_output = ema(state.bars.vwap, state.variables.MA)
|
||||
#trochu prasarna, EMAcko trunc na 3 mista - kdyz se osvedci, tak udelat efektivne
|
||||
ema_output = [trunc(i,3) for i in ema_output]
|
||||
print("emacko na wvap",state.variables.MA,":", ema_output[-5:])
|
||||
except:
|
||||
print("No data for MA yet")
|
||||
|
||||
print("MA is falling",state.variables.Trend,"value:",isfalling(ema_output,state.variables.Trend))
|
||||
print("MA is rising",state.variables.Trend,"value:",isrising(ema_output,state.variables.Trend))
|
||||
|
||||
## testuje aktualni cenu od nejvyssi visici limitky
|
||||
##toto spoustet jednou za X iterací - ted to jede pokazdé
|
||||
#pokud to ujede o vic, rusime limitky
|
||||
#TODO: zvazit jestli nechat i pri otevrenych pozicich, zatim nechavame
|
||||
#TODO int(int(state.oa.poz)/int(state.variables.chunk)) > X
|
||||
if state.oe.stratvars['jevylozeno'] == 1: # and int(state.oe.poz) == 0:
|
||||
#pokud mame vylozeno a cena je vetsi nez 0.04
|
||||
if len(state.oe.stratvars['pendingbuys'])>0:
|
||||
a = max(state.oe.stratvars['pendingbuys'].values())
|
||||
print("max cena v orderbuys", a)
|
||||
if float(ltp.price[state.oe.symbol]) > float(a) + ticks2reset:
|
||||
print("ujelo to vice nez o 4, rusime limit buye")
|
||||
state.oe.cancel_pending_buys()
|
||||
state.oe.stratvars['jevylozeno'] = 0
|
||||
|
||||
#pokud je vylozeno a mame pozice a neexistuje limitka - pak ji vytvorim
|
||||
if int(state.oe.poz)>0 and state.oe.limitka == 0:
|
||||
#pro jistotu updatujeme pozice
|
||||
state.oe.avgp, state.oe.poz = state.oe.pos()
|
||||
if int(state.oe.poz) > 0:
|
||||
cena = round(float(state.oe.avgp) + float(state.oe.stratvars["profit"]),2)
|
||||
print("BUGF: limitka neni vytvarime, a to za cenu",cena,"mnozstvi",state.oe.poz)
|
||||
print("aktuzalni ltp",ltp.price[state.oe.symbol])
|
||||
|
||||
try:
|
||||
state.oe.limitka = state.oe.sell_noasync(cena, state.oe.poz)
|
||||
print("vytvorena limitka", state.oe.limitka)
|
||||
except Exception as e:
|
||||
print("Neslo vytvorit profitku. Problem,ale jedeme dal",str(e))
|
||||
pass
|
||||
##raise Exception(e)
|
||||
|
||||
if state.oe.stratvars['jevylozeno'] == 0:
|
||||
print("neni vylozeno. Muzeme nakupovat")
|
||||
# testuji variantu, ze kupuji okamzite, nehlede na vyvoj
|
||||
if isfalling(ema_output,state.variables.Trend): # or 1==1:
|
||||
## vyloz pro pocet pozic (maximalni minus aktualni)
|
||||
#kolik nam zbyva pozic
|
||||
|
||||
#HOKUS: vykladame pouze polovinu pozic - dalsi polovinu davame v dalsimrunu
|
||||
# a = available pozice a/2
|
||||
a = int(int(state.variables.maxpozic)/int(state.variables.chunk)-(int(state.oe.poz)/int(state.variables.chunk)))
|
||||
a = int(a)
|
||||
print("Vykladame na pocet pozic", a)
|
||||
|
||||
|
||||
vyloz(pozic=a)
|
||||
#blokneme nakupy na dalsi bar
|
||||
state.variables.blockbuy = 1
|
||||
state.oe.stratvars['jevylozeno'] = 1
|
||||
|
||||
|
||||
#ulozime id baru
|
||||
# state.variables.lastbuyindex = state.bars.index[-1]
|
||||
|
||||
# je vylozeno
|
||||
else:
|
||||
## po kazde 4te pozici delame revykladani na aktualni zbytek pozic
|
||||
if (int(state.oe.poz)/(int(state.variables.chunk)) % 4 == 0):
|
||||
print("ctvrta pozice - v budoucnu realignujeme")
|
||||
# state.oe.cancel_pending_buys()
|
||||
# ##smazeme ihned pole - necekame na notifiaci
|
||||
# vyloz((int(state.variables.maxpozic)-int(state.oe.poz)/state.variables.chunk))
|
||||
|
||||
|
||||
#kazdy potvrzeny bar dotahneme pro jistotu otevřené objednávky a nahradíme si stav aktuálním
|
||||
|
||||
#pro jistotu update pozic - kdyz se nic nedeje nedeje
|
||||
#pripadne dat pryc
|
||||
#print("upatujeme pozice")
|
||||
#state.oe.avgp, state.oe.poz = state.oe.pos()
|
||||
|
||||
def init(state: StrategyState):
|
||||
print("init - zatim bez data")
|
||||
print(state.oe.symbol)
|
||||
print(state.oe.pos())
|
||||
print()
|
||||
|
||||
def main():
|
||||
stratvars = dict(maxpozic = 2000, chunk = 20, MA = 3, Trend = 4, profit = 0.01, blockbuy=0, lastbuyindex=0, pendingbuys={}, jevylozeno=0)
|
||||
s = MyStrategy("BAC",paper=PAPER, next=next, init=init, stratvars=stratvars)
|
||||
s.add_data(symbol="BAC",rectype=RecordType.CBAR,timeframe=12,filters=None,update_ltp=True,align=StartBarAlign.ROUND,mintick=4)
|
||||
|
||||
s.start()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
0
v2realbot/__init__.py
Normal file
0
v2realbot/__init__.py
Normal file
BIN
v2realbot/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
v2realbot/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
v2realbot/__pycache__/config.cpython-310.pyc
Normal file
BIN
v2realbot/__pycache__/config.cpython-310.pyc
Normal file
Binary file not shown.
0
v2realbot/backtesting/__init__.py
Normal file
0
v2realbot/backtesting/__init__.py
Normal file
BIN
v2realbot/backtesting/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
v2realbot/backtesting/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
v2realbot/backtesting/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
v2realbot/backtesting/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
v2realbot/backtesting/__pycache__/backtester.cpython-310.pyc
Normal file
BIN
v2realbot/backtesting/__pycache__/backtester.cpython-310.pyc
Normal file
Binary file not shown.
BIN
v2realbot/backtesting/__pycache__/backtester.cpython-311.pyc
Normal file
BIN
v2realbot/backtesting/__pycache__/backtester.cpython-311.pyc
Normal file
Binary file not shown.
816
v2realbot/backtesting/backtester.py
Normal file
816
v2realbot/backtesting/backtester.py
Normal file
@ -0,0 +1,816 @@
|
||||
#import os,sys
|
||||
#sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
"""
|
||||
Backtester component, allows to:
|
||||
|
||||
pro lepší trasovatelnost máme následující razítka
|
||||
open orders
|
||||
- submitted_at
|
||||
trades
|
||||
- submitted_at
|
||||
- filled_at
|
||||
|
||||
execute_orders_and_callbacks(time)
|
||||
- called before iteration
|
||||
- execute open orders before "time" and calls notification callbacks
|
||||
|
||||
market buy
|
||||
limit buy
|
||||
market sell
|
||||
limit sell
|
||||
cancel order by id
|
||||
replace order by id
|
||||
get positions
|
||||
|
||||
STATUSES supported:
|
||||
- FILLED (including callbacks)
|
||||
|
||||
not supported: NEW, ACCEPTED, CANCELED (currently no callback action will be backtestable on those)
|
||||
- případné nad těmito lze dát do synchronní části (api vrací rovnou zda objednávka neexistuje, pokud existuje tak předpokládáme vyplnění)
|
||||
|
||||
supports standard validations and blocking of share and cash upon order submit
|
||||
supports only GTC order validity
|
||||
no partial fills
|
||||
RETURN
|
||||
1 - ok
|
||||
0 - ok, noaction
|
||||
- 1 - error
|
||||
|
||||
"""
|
||||
from uuid import UUID, uuid4
|
||||
from alpaca.trading.enums import OrderSide, OrderStatus, TradeEvent, OrderType
|
||||
from v2realbot.common.model import TradeUpdate, Order
|
||||
#from rich import print
|
||||
import threading
|
||||
import asyncio
|
||||
from v2realbot.config import BT_DELAYS
|
||||
from v2realbot.utils.utils import AttributeDict, ltp, zoneNY, trunc, count_decimals,print
|
||||
from v2realbot.utils.tlog import tlog
|
||||
from datetime import datetime, timedelta
|
||||
import pandas as pd
|
||||
#import matplotlib.pyplot as plt
|
||||
#import seaborn; seaborn.set()
|
||||
#import mplfinance as mpf
|
||||
import plotly.graph_objects as go
|
||||
from plotly.subplots import make_subplots
|
||||
import numpy as np
|
||||
from bisect import bisect_left
|
||||
from v2realbot.utils.dash_save_html import make_static
|
||||
import dash_bootstrap_components as dbc
|
||||
from dash.dependencies import Input, Output
|
||||
from dash import dcc, html, dash_table, Dash
|
||||
from config import DATA_DIR
|
||||
""""
|
||||
LATENCY DELAYS
|
||||
.000 trigger - last_trade_time (.4246266)
|
||||
+.020 vstup do strategie a BUY (.444606)
|
||||
+.023 submitted (.469198)
|
||||
+.008 filled (.476695552)
|
||||
+.023 fill not(.499888)
|
||||
"""
|
||||
lock = threading.Lock
|
||||
|
||||
#todo nejspis dat do classes, aby se mohlo backtestovat paralelne
|
||||
#ted je globalni promena last_time_now a self.account a cash
|
||||
class Backtester:
|
||||
def __init__(self, symbol: str, order_fill_callback: callable, btdata: list, bp_from: datetime, bp_to: datetime, cash: float = 100000):
|
||||
#this TIME value determines true time for submit, replace, cancel order should happen (allowing past)
|
||||
#it is set by every iteration of BT or before fill callback - allowing past events to happen
|
||||
self.time = None
|
||||
self.symbol = symbol
|
||||
self.order_fill_callback = order_fill_callback
|
||||
self.btdata = btdata
|
||||
self.backtest_start = None
|
||||
self.backtest_end = None
|
||||
self.cash_init = cash
|
||||
#backtesting period
|
||||
self.bp_from = bp_from
|
||||
self.bp_to = bp_to
|
||||
self.cash = cash
|
||||
self.trades = []
|
||||
self.account = { "BAC": [0, 0] }
|
||||
# { "BAC": [avgp, size] }
|
||||
self.open_orders =[]
|
||||
# self.open_orders = [Order(id=uuid4(),
|
||||
# submitted_at = datetime(2023, 3, 17, 9, 30, 0, 0, tzinfo=zoneNY),
|
||||
# symbol = "BAC",
|
||||
# qty = 1,
|
||||
# status = OrderStatus.ACCEPTED,
|
||||
# order_type = OrderType.LIMIT,
|
||||
# side = OrderSide.BUY,
|
||||
# limit_price=22.4),
|
||||
# Order(id=uuid4(),
|
||||
# submitted_at = datetime(2023, 3, 17, 9, 30, 00, 0, tzinfo=zoneNY),
|
||||
# symbol = "BAC",
|
||||
# qty = 1,
|
||||
# order_type = OrderType.MARKET,
|
||||
# status = OrderStatus.ACCEPTED,
|
||||
# side = OrderSide.BUY)]
|
||||
|
||||
#
|
||||
def execute_orders_and_callbacks(self, intime: float):
|
||||
"""""
|
||||
Voláno ze strategie před každou iterací s časem T.
|
||||
Provede exekuci otevřených objednávek, které by se v reálu vyplnili do tohoto času.
|
||||
Pro vyplněné posílá fill notifikaci a volá callback s časem FILLu.
|
||||
|
||||
- current time - state.time
|
||||
- actual prices - self.btdata
|
||||
- callback after order fill - self.order_fill_callback
|
||||
- set time for order fill callback - self.time
|
||||
"""""
|
||||
|
||||
print(10*"*",intime,"Exec OPEN ORDERS ",len(self.open_orders)," time", datetime.fromtimestamp(intime).astimezone(zoneNY),10*"*")
|
||||
# print("cash before", cash)
|
||||
# print("BT: executing pending orders")
|
||||
# print("list before execution", self.open_orders)
|
||||
|
||||
if len(self.open_orders) == 0:
|
||||
ic("no pend orders")
|
||||
return 0
|
||||
|
||||
changes = 0
|
||||
|
||||
#iterating while removing items - have to use todel list
|
||||
todel = []
|
||||
#with lock:
|
||||
"""
|
||||
Pripravime si pracovni list
|
||||
btdata obsahuje vsechny aktualni timestampy tradu a jejich cenu.
|
||||
1) pracujeme vzdy OD zacatku listu DO indexu odpovidajici aktualnimu casu
|
||||
2) zjistime si index a pak iterujeme nad nim
|
||||
3) po skonceni pak tento pracovni kus umazeme
|
||||
"""
|
||||
|
||||
"""
|
||||
Assumes myList is sorted. Returns first biggeer value to the number.
|
||||
"""
|
||||
index_end = bisect_left(self.btdata, (intime,))
|
||||
|
||||
# #find index of state.time in btdata - nahrazeno BISECT
|
||||
# for index_end in range(len(self.btdata)):
|
||||
# #print(btdata[i][0])
|
||||
# #print(i)
|
||||
# if self.btdata[index_end][0] >= intime:
|
||||
# break
|
||||
|
||||
#pracovni list
|
||||
ic("work_range 0:index_end")
|
||||
ic(index_end)
|
||||
work_range = self.btdata[0:index_end]
|
||||
ic(len(work_range))
|
||||
#print("index_end", i)
|
||||
#print("oriznuto",self.btdata[0:i+1])
|
||||
|
||||
for order in self.open_orders:
|
||||
#pokud je vyplneny symbol, tak jedeme jen tyto, jinak vsechny
|
||||
print(order.id, datetime.timestamp(order.submitted_at), order.symbol, order.side, order.order_type, order.qty, order.limit_price, order.submitted_at)
|
||||
if order.canceled_at:
|
||||
ic("deleting canceled order",order.id)
|
||||
todel.append(order)
|
||||
elif not self.symbol or order.symbol == self.symbol:
|
||||
#pricteme mininimalni latency od submittu k fillu
|
||||
if order.submitted_at.timestamp() + BT_DELAYS.sub_to_fill > float(intime):
|
||||
ic("too soon for",order.id)
|
||||
#try to execute
|
||||
else:
|
||||
#try to execute specific order
|
||||
a = self._execute_order(o = order, intime=intime, work_range=work_range)
|
||||
if a == 1:
|
||||
ic("EXECUTED")
|
||||
todel.append(order)
|
||||
changes = 1
|
||||
else:
|
||||
ic("NOT EXECUTED",a)
|
||||
#ic("istodel",todel)
|
||||
#vymazu z pending orderu vschny zprocesovane nebo ty na výmaz
|
||||
for i in todel:
|
||||
self.open_orders.remove(i)
|
||||
todel = {}
|
||||
|
||||
#tady udelat pripadny ořez self.btdata - priste zaciname od zacatku
|
||||
#ic("before delete", len(self.btdata))
|
||||
|
||||
#TEST zkusime to nemazat, jak ovlivni performance
|
||||
#Mazeme, jinak je to hruza
|
||||
del self.btdata[0:index_end-2]
|
||||
#ic("after delete",len(self.btdata[0:index_end]))
|
||||
|
||||
if changes: return 1
|
||||
else: return 0
|
||||
# print("pending orders after execution", self.open_orders)
|
||||
# print("trades after execution", trades)
|
||||
# print("self.account after execution", self.account)
|
||||
# print("cash after", cash)
|
||||
|
||||
def _execute_order(self, o: Order, intime: float, work_range):
|
||||
"""tries to execute order
|
||||
|
||||
o - specific Order
|
||||
time - intime
|
||||
work_range - aktualni slice of btdata pro tuto iteraci [(timestamp,price)] """
|
||||
|
||||
fill_time = None
|
||||
fill_price = None
|
||||
order_min_fill_time = o.submitted_at.timestamp() + BT_DELAYS.sub_to_fill
|
||||
ic(order_min_fill_time)
|
||||
ic(len(work_range))
|
||||
|
||||
if o.order_type == OrderType.LIMIT:
|
||||
if o.side == OrderSide.BUY:
|
||||
for i in work_range:
|
||||
#print(i)
|
||||
##najde prvni nejvetsi čas vetsi nez minfill a majici odpovídající cenu
|
||||
## pro LIMITku nejspíš přidat BT_DELAY.LIMIT_OFFSET, aby se nevyplnilo hned jako prvni s touto cenou
|
||||
## offest by se pocital od nize nalezeneho casu, zvetsil by ho o LIMIT_OFFSET a zjistil, zda by
|
||||
##v novem case doslo take k plneni a tam ho vyplnil. Uvidime az jestli bude aktualni prilis optimisticke.
|
||||
## TBD zjistit na LIVE jaky je tento offset
|
||||
if float(i[0]) > float(order_min_fill_time+BT_DELAYS.limit_order_offset) and i[1] <= o.limit_price:
|
||||
#(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])
|
||||
break
|
||||
else:
|
||||
for i in work_range:
|
||||
#print(i)
|
||||
if float(i[0]) > float(order_min_fill_time+BT_DELAYS.limit_order_offset) and i[1] >= o.limit_price:
|
||||
#(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])
|
||||
break
|
||||
|
||||
elif o.order_type == OrderType.MARKET:
|
||||
for i in work_range:
|
||||
#print(i)
|
||||
#najde prvni nejvetsi čas vetsi nez minfill
|
||||
if i[0] > float(order_min_fill_time):
|
||||
#(1679081919.381649, 27.88)
|
||||
ic(i)
|
||||
fill_time = i[0]
|
||||
fill_price = i[1]
|
||||
print("FILL ",o.side,"MARKET at", fill_time, datetime.fromtimestamp(fill_time).astimezone(zoneNY), "cena", i[1])
|
||||
break
|
||||
else:
|
||||
print("unknown order type")
|
||||
return -1
|
||||
|
||||
if not fill_time:
|
||||
ic("not FILLED")
|
||||
return 0
|
||||
else:
|
||||
|
||||
#order FILLED - update trades and account and cash
|
||||
o.filled_at = datetime.fromtimestamp(float(fill_time))
|
||||
o.filled_qty = o.qty
|
||||
o.filled_avg_price = float(fill_price)
|
||||
o.status = OrderStatus.FILLED
|
||||
|
||||
ic(o.filled_at, o.filled_avg_price)
|
||||
|
||||
a = self.update_account(o = o)
|
||||
if a < 0:
|
||||
tlog("update_account ERROR")
|
||||
return -1
|
||||
|
||||
trade = TradeUpdate(order = o,
|
||||
event = TradeEvent.FILL,
|
||||
execution_id = str(uuid4()),
|
||||
timestamp = datetime.fromtimestamp(fill_time),
|
||||
position_qty= self.account[o.symbol][0],
|
||||
price=float(fill_price),
|
||||
qty = o.qty,
|
||||
value = float(o.qty*fill_price),
|
||||
cash = self.cash,
|
||||
pos_avg_price = self.account[o.symbol][1])
|
||||
|
||||
self.trades.append(trade)
|
||||
|
||||
# do notification with callbacks
|
||||
#print("pred appendem",self.open_orders)
|
||||
self._do_notification_with_callbacks(tradeupdate=trade, time=float(fill_time))
|
||||
#print("po appendu",self.open_orders)
|
||||
#ohandlovat nejak chyby? v LIVE je to asynchronni a takze neni ohandlovano, takze jen print
|
||||
return 1
|
||||
|
||||
def _do_notification_with_callbacks(self, tradeupdate: TradeUpdate, time: float):
|
||||
|
||||
#do callbacku je třeba zpropagovat filltime čas (včetně latency pro notifikaci), aby se pripadne akce v callbacku udály s tímto časem
|
||||
self.time = time + float(BT_DELAYS.fill_to_not)
|
||||
print("current bt.time",self.time)
|
||||
#print("FILL NOTIFICATION: ", tradeupdate)
|
||||
res = asyncio.run(self.order_fill_callback(tradeupdate))
|
||||
return 0
|
||||
|
||||
def update_account(self, o: Order):
|
||||
#updatujeme self.account
|
||||
#pokud neexistuje klic v accountu vytvorime si ho
|
||||
if o.symbol not in self.account:
|
||||
# { "BAC": [size, avgp] }
|
||||
self.account[o.symbol] = [0,0]
|
||||
|
||||
if o.side == OrderSide.BUY:
|
||||
#[size, avgp]
|
||||
if (self.account[o.symbol][0] + o.qty) == 0: newavgp = 0
|
||||
else:
|
||||
newavgp = ((self.account[o.symbol][0] * self.account[o.symbol][1]) + (o.qty * o.filled_avg_price)) / (self.account[o.symbol][0] + o.qty)
|
||||
self.account[o.symbol] = [self.account[o.symbol][0]+o.qty, newavgp]
|
||||
self.cash = self.cash - (o.qty * o.filled_avg_price)
|
||||
return 1
|
||||
#sell
|
||||
elif o.side == OrderSide.SELL:
|
||||
newsize = self.account[o.symbol][0]-o.qty
|
||||
if newsize == 0: newavgp = 0
|
||||
else:
|
||||
if self.account[o.symbol][1] == 0:
|
||||
newavgp = o.filled_avg_price
|
||||
else:
|
||||
newavgp = self.account[o.symbol][1]
|
||||
self.account[o.symbol] = [newsize, newavgp]
|
||||
self.cash = float(self.cash + (o.qty * o.filled_avg_price))
|
||||
return 1
|
||||
else:
|
||||
print("neznaama side", o.side)
|
||||
return -1
|
||||
|
||||
"""""
|
||||
BACKEND PRO API
|
||||
|
||||
TODO - možná toto předělat a brát si čas z bt.time - upravit také v BacktestInterface
|
||||
BUG:
|
||||
"""""
|
||||
|
||||
def get_last_price(self, time: float, symbol: str = None):
|
||||
"""""
|
||||
returns equity price in timestamp. Assumes myList is sorted. Returns first lower value to the number.
|
||||
optimalized as bisect left
|
||||
"""""
|
||||
pos = bisect_left(self.btdata, (time,))
|
||||
ic("vracime last price")
|
||||
ic(time)
|
||||
if pos == 0:
|
||||
ic(self.btdata[0][1])
|
||||
return self.btdata[0][1]
|
||||
afterTime, afterPrice = self.btdata[pos-1]
|
||||
ic(afterPrice)
|
||||
return afterPrice
|
||||
|
||||
|
||||
#not optimalized:
|
||||
# for i in range(len(self.btdata)):
|
||||
# #print(btdata[i][0])
|
||||
# #print(i)
|
||||
# if self.btdata[i][0] >= time:
|
||||
# break
|
||||
# ic(time, self.btdata[i-1][1])
|
||||
# ic("get last price")
|
||||
# return self.btdata[i-1][1]
|
||||
|
||||
def submit_order(self, time: float, symbol: str, side: OrderSide, size: int, order_type: OrderType, price: float = None):
|
||||
"""submit order
|
||||
- zakladni validace
|
||||
- vloží do self.open_orders s daným časem
|
||||
- vrátí orderID
|
||||
|
||||
status NEW se nenotifikuje
|
||||
|
||||
TBD dotahovani aktualni ceny podle backtesteru
|
||||
"""
|
||||
print("BT: submit order entry")
|
||||
|
||||
if not time or time < 0:
|
||||
print("time musi byt vyplneny")
|
||||
return -1
|
||||
|
||||
if not size or int(size) < 0:
|
||||
print("size musi byt vetsi nez 0")
|
||||
return -1
|
||||
|
||||
if (order_type != OrderType.MARKET) and (order_type != OrderType.LIMIT):
|
||||
tlog("ordertype market and limit supported only", order_type)
|
||||
return -1
|
||||
|
||||
if not side == OrderSide.BUY and not side == OrderSide.SELL:
|
||||
print("side buy/sell required")
|
||||
return -1
|
||||
|
||||
if order_type == OrderType.LIMIT and count_decimals(price) > 2:
|
||||
print("only 2 decimals supported", price)
|
||||
return -1
|
||||
|
||||
#pokud neexistuje klic v accountu vytvorime si ho
|
||||
if symbol not in self.account:
|
||||
# { "BAC": [size, avgp] }
|
||||
self.account[symbol] = [0,0]
|
||||
|
||||
#check for available quantity
|
||||
if side == OrderSide.SELL:
|
||||
reserved = 0
|
||||
#with lock:
|
||||
for o in self.open_orders:
|
||||
if o.qty == OrderSide.SELL and o.symbol == symbol:
|
||||
reserved += o.qty
|
||||
#print("blokovano v open orders pro sell: ", reserved)
|
||||
|
||||
if int(self.account[symbol][0]) - reserved - int(size) < 0:
|
||||
print("not enough shares having",self.account[symbol][0],"reserved",reserved,"available",int(self.account[symbol][0]) - reserved,"selling",size)
|
||||
return -1
|
||||
|
||||
#check for available cash
|
||||
if side == OrderSide.BUY:
|
||||
reserved = 0
|
||||
#with lock:
|
||||
for o in self.open_orders:
|
||||
if o.qty == OrderSide.BUY:
|
||||
cena = o.limit_price if o.limit_price else self.get_last_price(time, o.symbol)
|
||||
reserved += o.qty * cena
|
||||
#print("blokovano v open orders: ", reserved)
|
||||
|
||||
cena = price if price else self.get_last_price(time, self.symbol)
|
||||
if (self.cash - reserved - float(int(size)*float(cena))) < 0:
|
||||
print("not enough cash. cash",self.cash,"reserved",reserved,"available",self.cash-reserved,"needed",float(int(size)*float(cena)))
|
||||
return -1
|
||||
|
||||
id = str(uuid4())
|
||||
order = Order(id=id,
|
||||
submitted_at = datetime.fromtimestamp(float(time)),
|
||||
symbol=symbol,
|
||||
order_type = order_type,
|
||||
status = OrderStatus.ACCEPTED,
|
||||
side=side,
|
||||
qty=int(size),
|
||||
limit_price=(float(price) if price else None))
|
||||
|
||||
self.open_orders.append(order)
|
||||
ic("order SUBMITTED", order)
|
||||
return id
|
||||
|
||||
|
||||
def replace_order(self, id: str, time: float, size: int = None, price: float = None):
|
||||
"""replace order
|
||||
- zakladni validace vrací synchronně
|
||||
- vrací číslo nové objednávky
|
||||
"""
|
||||
print("BT: replace order entry",id,size,price)
|
||||
|
||||
if not price and not size:
|
||||
print("size or price required")
|
||||
return -1
|
||||
|
||||
if len(self.open_orders) == 0:
|
||||
print("BT: order doesnt exist")
|
||||
return 0
|
||||
#with lock:
|
||||
for o in self.open_orders:
|
||||
print(o.id)
|
||||
if str(o.id) == str(id):
|
||||
newid = str(uuid4())
|
||||
o.id = newid
|
||||
o.submitted_at = datetime.fromtimestamp(time)
|
||||
o.qty = int((size if size else o.qty))
|
||||
o.limit_price = float(price if price else o.limit_price)
|
||||
print("order replaced")
|
||||
return newid
|
||||
print("BT: replacement order doesnt exist")
|
||||
return 0
|
||||
|
||||
def cancel_order(self, time: float, id: str):
|
||||
"""cancel order
|
||||
- základní validace vrací synchronně
|
||||
- vymaže objednávku z open orders
|
||||
- nastavuje v open orders flag zrušeno, který pak exekuce ignoruje a odstraní
|
||||
(je tak podchycen stav, kdy se cancel volá z bt callbacku a z iterovaného listu by se odstraňovalo během iterace)
|
||||
|
||||
TODO: teoreticky bych pred cancel order mohl zavolat execution, abych vyloucil, ze za 20ms zpozdeni, kdy se vola alpaca mi neprojde nejaka objednavka
|
||||
spise do budoucna
|
||||
"""
|
||||
print("BT: cancel order entry",id)
|
||||
if len(self.open_orders) == 0:
|
||||
print("BTC: order doesnt exist")
|
||||
return 0
|
||||
#with lock:
|
||||
for o in self.open_orders:
|
||||
if str(o.id) == id:
|
||||
o.canceled_at = time
|
||||
print("set as canceled in self.open_orders")
|
||||
return 1
|
||||
print("BTC: cantchange. open order doesnt exist")
|
||||
return 0
|
||||
|
||||
def get_open_position(self, symbol: str):
|
||||
"""get positions ->(avg,size)"""
|
||||
#print("BT:get open positions entry")
|
||||
try:
|
||||
return self.account[symbol][1], self.account[symbol][0]
|
||||
except:
|
||||
return (0,0)
|
||||
|
||||
def get_open_orders(self, side: OrderSide, symbol: str):
|
||||
"""get open orders ->list(OrderNotification)"""
|
||||
print("BT:get open orders entry")
|
||||
if len(self.open_orders) == 0:
|
||||
print("BTC: order doesnt exist")
|
||||
return 0
|
||||
res = []
|
||||
#with lock:
|
||||
for o in self.open_orders:
|
||||
if str(o.side) == side and o.symbol == symbol:
|
||||
res.append(o)
|
||||
return res
|
||||
|
||||
def display_backtest_result(self, state):
|
||||
"""
|
||||
Displays backtest results chart, trades and orders with option to save the result as static HTML.
|
||||
"""
|
||||
#open_orders to dataset
|
||||
oo_dict = AttributeDict(orderid=[],submitted_at=[],symbol=[],side=[],order_type=[],qty=[],limit_price=[],status=[])
|
||||
for t in self.open_orders:
|
||||
oo_dict.orderid.append(str(t.id))
|
||||
oo_dict.submitted_at.append(t.submitted_at)
|
||||
oo_dict.symbol.append(t.symbol)
|
||||
oo_dict.side.append(t.side)
|
||||
oo_dict.qty.append(t.qty)
|
||||
oo_dict.order_type.append(t.order_type)
|
||||
oo_dict.limit_price.append(t.limit_price)
|
||||
oo_dict.status.append(t.status)
|
||||
|
||||
open_orders_df = pd.DataFrame(oo_dict)
|
||||
open_orders_df = open_orders_df.set_index('submitted_at', drop=False)
|
||||
|
||||
#trades to dataset
|
||||
trade_dict = AttributeDict(orderid=[],timestamp=[],symbol=[],side=[],order_type=[],qty=[],price=[],position_qty=[],value=[],cash=[],pos_avg_price=[])
|
||||
for t in self.trades:
|
||||
trade_dict.orderid.append(str(t.order.id))
|
||||
trade_dict.timestamp.append(t.timestamp)
|
||||
trade_dict.symbol.append(t.order.symbol)
|
||||
trade_dict.side.append(t.order.side)
|
||||
trade_dict.qty.append(t.qty)
|
||||
trade_dict.price.append(t.price)
|
||||
trade_dict.position_qty.append(t.position_qty)
|
||||
trade_dict.value.append(t.value)
|
||||
trade_dict.cash.append(t.cash)
|
||||
trade_dict.order_type.append(t.order.order_type)
|
||||
trade_dict.pos_avg_price.append(t.pos_avg_price)
|
||||
|
||||
trade_df = pd.DataFrame(trade_dict)
|
||||
trade_df = trade_df.set_index('timestamp',drop=False)
|
||||
|
||||
#ohlcv dataset (TODO podporit i trady)
|
||||
hist_df = pd.DataFrame(state.bars)
|
||||
hist_df = hist_df.set_index('time', drop=False)
|
||||
|
||||
#indicators
|
||||
ind_df = pd.DataFrame(state.indicators)
|
||||
ind_df = ind_df.set_index('time', drop=False)
|
||||
#print("Indicators", ind_df)
|
||||
#print(state.indicators)
|
||||
|
||||
#KONEC přípravy dat
|
||||
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.02, row_heights=[0.7, 0.3], specs=[[{"secondary_y": True}],[{"secondary_y": True}]])
|
||||
|
||||
|
||||
# fig.add_trace(go.Scatter(x=trade_df.index,
|
||||
# y=trade_df.cash,
|
||||
# mode="lines+text",
|
||||
# name="Value"),
|
||||
# row = 1, col=1, secondary_y=True)
|
||||
|
||||
#add_openorders
|
||||
fig.add_trace(go.Scatter(x=open_orders_df.index,
|
||||
y=open_orders_df.limit_price,
|
||||
mode="markers+text",
|
||||
name="Open orders",
|
||||
customdata=open_orders_df.orderid,
|
||||
marker=dict(size=17, color='blue', symbol='arrow-bar-down'),
|
||||
text=open_orders_df.qty),
|
||||
row = 1, col=1, secondary_y=False)
|
||||
|
||||
#add trades
|
||||
fig.add_trace(go.Scatter(x=trade_df.loc[trade_df.side==OrderSide.BUY].index,
|
||||
y=trade_df.loc[trade_df.side==OrderSide.BUY].price,
|
||||
mode="markers+text",
|
||||
name="BUY Trades",
|
||||
customdata=trade_df.loc[trade_df.side==OrderSide.BUY].orderid,
|
||||
marker=dict(size=15, color='green', symbol='arrow-up'),
|
||||
text=trade_df.loc[trade_df.side==OrderSide.BUY].position_qty),
|
||||
row = 1, col=1, secondary_y=False)
|
||||
|
||||
fig.add_trace(go.Scatter(x=trade_df.loc[trade_df.side==OrderSide.SELL].index,
|
||||
y=trade_df.loc[trade_df.side==OrderSide.SELL].price,
|
||||
mode="markers+text",
|
||||
name="SELL Trades",
|
||||
customdata=trade_df.loc[trade_df.side==OrderSide.SELL].orderid,
|
||||
marker=dict(size=15, color='red', symbol='arrow-down'),
|
||||
text=trade_df.loc[trade_df.side==OrderSide.SELL].qty),
|
||||
row = 1, col=1, secondary_y=False)
|
||||
|
||||
#add avgprices of all buy trades
|
||||
|
||||
fig.add_trace(go.Scatter(x=trade_df.loc[trade_df.side==OrderSide.BUY].index,
|
||||
y=trade_df.loc[trade_df.side==OrderSide.BUY].pos_avg_price,
|
||||
mode="markers+text",
|
||||
name="BUY Trades avg prices",
|
||||
customdata=trade_df.loc[trade_df.side==OrderSide.BUY].orderid,
|
||||
marker=dict(size=15, color='blue', symbol='diamond-wide'),
|
||||
text=trade_df.loc[trade_df.side==OrderSide.BUY].position_qty),
|
||||
row = 1, col=1, secondary_y=False)
|
||||
|
||||
#display ohlcv
|
||||
fig.add_trace(go.Candlestick(x=hist_df.index,
|
||||
open=hist_df['open'],
|
||||
high=hist_df['high'],
|
||||
low=hist_df['low'],
|
||||
close=hist_df['close'],
|
||||
name = "OHLC"),
|
||||
row = 1, col=1, secondary_y=False)
|
||||
|
||||
#addvwap
|
||||
fig.add_trace(go.Scatter(x=hist_df.index,
|
||||
y=hist_df.vwap,
|
||||
mode="lines",
|
||||
opacity=1,
|
||||
name="VWAP"
|
||||
),
|
||||
row = 1, col=1,secondary_y=False)
|
||||
|
||||
|
||||
#display indicators from history
|
||||
for col in ind_df.columns:
|
||||
fig.add_trace(go.Scatter(x=ind_df.index, y = ind_df[col], mode = 'lines', name = col),
|
||||
row = 1, col=1, secondary_y=False)
|
||||
|
||||
fig.add_trace(go.Bar(x=hist_df.index, y=hist_df.volume, showlegend=True, marker_color='#ef5350', name='Volume'), row=2,
|
||||
col=1)
|
||||
|
||||
fig.update_layout(xaxis_rangeslider_visible=False)
|
||||
#fig.update_layout(title=f'Backtesting Results '+str(Neco.vars), yaxis_title=f'Price')
|
||||
fig.update_layout(yaxis_title=f'Price')
|
||||
fig.update_yaxes(title_text=f'Price', secondary_y=False)
|
||||
fig.update_yaxes(title_text=f'Cash value', secondary_y=True)
|
||||
fig.update_yaxes(title_text=f'Volume', row=2, col=1)
|
||||
fig.update_xaxes(title_text='Date', row=2)
|
||||
|
||||
|
||||
# #remove gaps
|
||||
# alldays =set(hist_df.time[0]+timedelta(x) for x in range((hist_df.time[len(hist_df.time)-1]- hist_df.time[0]).days))
|
||||
# missing=sorted(set(alldays)-set(hist_df.time))
|
||||
|
||||
|
||||
rangebreaks=[
|
||||
# NOTE: Below values are bound (not single values), ie. hide x to y
|
||||
dict(bounds=["sat", "mon"]), # hide weekends, eg. hide sat to before mon
|
||||
dict(bounds=[22, 15.5], pattern="hour"), # hide hours outside of 9.30am-4pm
|
||||
# dict(values=["2020-12-25", "2021-01-01"]) # hide holidays (Christmas and New Year's, etc)
|
||||
]
|
||||
|
||||
fig.update_xaxes(rangebreaks=rangebreaks)
|
||||
|
||||
|
||||
##START DASH
|
||||
app = Dash(__name__)
|
||||
|
||||
## Define the title for the app
|
||||
mytitle = dcc.Markdown('# Backtesting results')
|
||||
button = html.Button('save static', id='save', n_clicks=0)
|
||||
saved = html.Span('', id='saved')
|
||||
textik1 = html.Div('''
|
||||
Strategy:''' + state.name)
|
||||
textik2 = html.Div('''
|
||||
Tested period:'''+ self.bp_from.strftime("%d/%m/%Y, %H:%M:%S") + '-' + self.bp_to.strftime("%d/%m/%Y, %H:%M:%S"))
|
||||
textik3 = html.Div('''
|
||||
Stratvars:'''+ str(state.vars))
|
||||
textik35 = html.Div('''
|
||||
Resolution:'''+ str(state.timeframe) + "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('''
|
||||
Cash start:''' + str(self.cash_init)+ " Cash final" + str(self.cash))
|
||||
textik55 = html.Div('''
|
||||
Positions:''' + str(self.account))
|
||||
textik6 = html.Div('''
|
||||
Open orders:''' + str(len(self.open_orders)))
|
||||
textik7 = html.Div('''
|
||||
Trades:''' + str(len(self.trades)))
|
||||
orders_title = dcc.Markdown('## Open orders')
|
||||
trades_title = dcc.Markdown('## Trades')
|
||||
## Define the graph
|
||||
mygraph= dcc.Graph(id = "hlavni-graf", figure=fig)
|
||||
|
||||
open_orders_table = dash_table.DataTable(
|
||||
id="orderstable",
|
||||
data=open_orders_df.to_dict('records'),
|
||||
columns=[{'id': c, 'name': c} for c in open_orders_df.columns],
|
||||
sort_action="native",
|
||||
row_selectable="single",
|
||||
column_selectable=False,
|
||||
fill_width = False,
|
||||
filter_action = "native",
|
||||
style_table={
|
||||
'height': 500,
|
||||
'overflowY': 'scroll'
|
||||
},
|
||||
style_header={
|
||||
'backgroundColor': 'lightgrey',
|
||||
'color': 'black'
|
||||
},
|
||||
style_data={
|
||||
'backgroundColor': 'white',
|
||||
'color': 'black'
|
||||
},
|
||||
style_cell={
|
||||
'overflow': 'hidden',
|
||||
'textOverflow': 'ellipsis',
|
||||
'maxWidth': 220,
|
||||
'minWidth': 5,
|
||||
'width': 5
|
||||
}
|
||||
)
|
||||
|
||||
trades_table = dash_table.DataTable(
|
||||
id="tradestable",
|
||||
data=trade_df.to_dict('records'),
|
||||
columns=[{'id': c, 'name': c} for c in trade_df.columns],
|
||||
sort_action="native",
|
||||
row_selectable="single",
|
||||
column_selectable=False,
|
||||
fill_width = False,
|
||||
filter_action = "native",
|
||||
style_table={
|
||||
'height': 500,
|
||||
'overflowY': 'scroll'
|
||||
},
|
||||
style_header={
|
||||
'backgroundColor': 'lightgrey',
|
||||
'color': 'black'
|
||||
},
|
||||
style_data={
|
||||
'backgroundColor': 'white',
|
||||
'color': 'black'
|
||||
},
|
||||
style_cell={
|
||||
'overflow': 'hidden',
|
||||
'textOverflow': 'ellipsis',
|
||||
'maxWidth': 220,
|
||||
'minWidth': 5,
|
||||
'width': 5
|
||||
}
|
||||
# page_size=15
|
||||
)
|
||||
|
||||
@app.callback(Output("tradestable", "style_data_conditional"),
|
||||
Input("hlavni-graf", "hoverData"))
|
||||
def highlight(hoverData):
|
||||
#print(hoverData)
|
||||
if hoverData is None:
|
||||
return None
|
||||
try:
|
||||
row = hoverData["points"][0]["customdata"]
|
||||
except KeyError:
|
||||
return None
|
||||
#print(row)
|
||||
|
||||
#print({"if": {"filter_query": "{{orderid}}={}".format(row)}, "backgroundColor": "lightgrey"})
|
||||
return [
|
||||
{"if": {"filter_query": "{{orderid}}={}".format(row)}, "backgroundColor": "lightgrey"}
|
||||
]
|
||||
@app.callback(Output("orderstable", "style_data_conditional"),
|
||||
Input("hlavni-graf", "hoverData"))
|
||||
def highlight(hoverData):
|
||||
#print(hoverData)
|
||||
if hoverData is None:
|
||||
return None
|
||||
try:
|
||||
row = hoverData["points"][0]["customdata"]
|
||||
except KeyError:
|
||||
return None
|
||||
#print(row)
|
||||
|
||||
#print({"if": {"filter_query": "{{orderid}}={}".format(row)}, "backgroundColor": "lightgrey"})
|
||||
return [
|
||||
{"if": {"filter_query": "{{orderid}}={}".format(row)}, "backgroundColor": "lightgrey"}
|
||||
]
|
||||
|
||||
@app.callback(
|
||||
Output('saved', 'children'),
|
||||
Input('save', 'n_clicks'),
|
||||
)
|
||||
def save_result(n_clicks):
|
||||
if n_clicks == 0:
|
||||
return 'not saved'
|
||||
else:
|
||||
bt_dir = DATADIR + "/backtestresults/" + self.symbol + self.bp_from.strftime("%d-%m-%y-%H-%M-%S") + ' ' + self.bp_to.strftime("%d-%m-%y-%H-%M-%S") + ' ' + str(datetime.now().microsecond)
|
||||
make_static(f'http://127.0.0.1:{port}/', bt_dir)
|
||||
return 'saved'
|
||||
|
||||
## Customize your layout
|
||||
app.layout = dbc.Container([mytitle,button,saved, textik1, textik2, textik3, textik35, textik4, textik5, textik55, textik6,textik7, mygraph, trades_title, trades_table, orders_title, open_orders_table])
|
||||
|
||||
port = 9050
|
||||
print("Backtest FINSIHED"+str(self.backtest_end-self.backtest_start))
|
||||
threading.Thread(target=lambda: app.run(port=port, debug=False, use_reloader=False)).start()
|
||||
#app.run_server(debug=False, port = port)
|
||||
print("tady se spouští server")
|
||||
print("Jedeme dal?")
|
||||
0
v2realbot/common/__init__.py
Normal file
0
v2realbot/common/__init__.py
Normal file
BIN
v2realbot/common/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
v2realbot/common/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
v2realbot/common/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
v2realbot/common/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
v2realbot/common/__pycache__/model.cpython-310.pyc
Normal file
BIN
v2realbot/common/__pycache__/model.cpython-310.pyc
Normal file
Binary file not shown.
BIN
v2realbot/common/__pycache__/model.cpython-311.pyc
Normal file
BIN
v2realbot/common/__pycache__/model.cpython-311.pyc
Normal file
Binary file not shown.
100
v2realbot/common/model.py
Normal file
100
v2realbot/common/model.py
Normal file
@ -0,0 +1,100 @@
|
||||
from uuid import UUID, uuid4
|
||||
from alpaca.trading.enums import OrderSide, OrderStatus, TradeEvent, OrderClass, OrderType, TimeInForce
|
||||
#from utils import AttributeDict
|
||||
from rich import print
|
||||
from typing import Any, Optional, List, Union
|
||||
from datetime import datetime, date
|
||||
from pydantic import BaseModel
|
||||
from v2realbot.enums.enums import Mode, Account
|
||||
|
||||
#tu samou variantu pak UpdateStrategyInstanceWhileRunning
|
||||
|
||||
#only those that can be changed UUID id prijde v parametru
|
||||
# @app.put("/api/v1/users/{id}")
|
||||
# async def update_user(user_update: UpdateUser, id: UUID):
|
||||
# for user in db:
|
||||
# if user.id == id:
|
||||
# if user_update.first_name is not None:
|
||||
# user.first_name = user_update.first_name
|
||||
# if user_update.last_name is not None:
|
||||
# user.last_name = user_update.last_name
|
||||
# if user_update.roles is not None:
|
||||
# user.roles = user_update.roles
|
||||
# return user.id
|
||||
# raise HTTPException(status_code=404, detail=f"Could not find user with id: {id}")
|
||||
|
||||
#persisted object in pickle
|
||||
class StrategyInstance(BaseModel):
|
||||
id: Optional[UUID | str | None] = None
|
||||
id2: int
|
||||
name: str
|
||||
symbol: str
|
||||
class_name: str
|
||||
script: str
|
||||
open_rush: int = 0
|
||||
close_rush: int = 0
|
||||
stratvars_conf: str
|
||||
add_data_conf: str
|
||||
note: Optional[str]
|
||||
history: Optional[str]
|
||||
|
||||
class RunRequest(BaseModel):
|
||||
id: UUID
|
||||
account: Account
|
||||
mode: Mode
|
||||
debug: bool = False
|
||||
bt_from: datetime = None
|
||||
bt_to: datetime = None
|
||||
cash: int = 100000
|
||||
|
||||
class RunnerView(BaseModel):
|
||||
id: UUID
|
||||
run_started: Optional[datetime] = None
|
||||
run_mode: Mode
|
||||
run_account: Account
|
||||
run_stopped: Optional[datetime] = None
|
||||
run_paused: Optional[datetime] = None
|
||||
|
||||
#Running instance - not persisted
|
||||
class Runner(BaseModel):
|
||||
id: UUID
|
||||
run_started: Optional[datetime] = None
|
||||
run_mode: Mode
|
||||
run_account: Account
|
||||
run_stopped: Optional[datetime] = None
|
||||
run_paused: Optional[datetime] = None
|
||||
run_thread: Optional[object] = None
|
||||
run_instance: Optional[object] = None
|
||||
run_pause_ev: Optional[object] = None
|
||||
run_stop_ev: Optional[object] = None
|
||||
|
||||
class Order(BaseModel):
|
||||
id: UUID
|
||||
submitted_at: datetime
|
||||
filled_at: Optional[datetime]
|
||||
canceled_at: Optional[datetime]
|
||||
symbol: str
|
||||
qty: int
|
||||
status: OrderStatus
|
||||
order_type: OrderType
|
||||
filled_qty: Optional[int]
|
||||
filled_avg_price: Optional[float]
|
||||
side: OrderSide
|
||||
limit_price: Optional[float]
|
||||
|
||||
class TradeUpdate(BaseModel):
|
||||
event: Union[TradeEvent, str]
|
||||
execution_id: Optional[UUID]
|
||||
order: Order
|
||||
timestamp: datetime
|
||||
position_qty: Optional[float]
|
||||
price: Optional[float]
|
||||
qty: Optional[float]
|
||||
value: Optional[float]
|
||||
cash: Optional[float]
|
||||
pos_avg_price: Optional[float]
|
||||
|
||||
# class Trade(BaseModel):
|
||||
# order: Order
|
||||
# value: float
|
||||
|
||||
100
v2realbot/conf.toml
Normal file
100
v2realbot/conf.toml
Normal file
@ -0,0 +1,100 @@
|
||||
[general]
|
||||
general_attributtes = true
|
||||
ping_time = 1200
|
||||
|
||||
#V1 na BAC
|
||||
[[strategies]]
|
||||
name = "V1 na BAC"
|
||||
symbol = "BAC"
|
||||
script = "ENTRY_backtest_strategyVykladaci"
|
||||
class = "StrategyOrderLimitVykladaci"
|
||||
open_rush = 0
|
||||
close_rush = 0
|
||||
|
||||
[stratvars]
|
||||
maxpozic = 200
|
||||
chunk = 10
|
||||
MA = 6
|
||||
Trend = 5
|
||||
profit = 0.02
|
||||
lastbuyindex=-6
|
||||
pendingbuys={}
|
||||
limitka = "None"
|
||||
jevylozeno=0
|
||||
vykladka=5
|
||||
curve = [0.01, 0.01, 0.01, 0.0, 0.02, 0.02, 0.01,0.01, 0.01,0.03, 0.01, 0.01, 0.01,0.04, 0.01,0.01, 0.01,0.05, 0.01,0.01, 0.01,0.01, 0.06,0.01, 0.01,0.01, 0.01]
|
||||
blockbuy = 0
|
||||
ticks2reset = 0.04
|
||||
|
||||
[[add_data]]
|
||||
symbol="BAC"
|
||||
rectype= "bar"
|
||||
timeframe=5
|
||||
update_ltp=true
|
||||
align="round"
|
||||
mintick=0
|
||||
minsize=100
|
||||
exthours=false
|
||||
|
||||
#D2 na C
|
||||
[[strategies]]
|
||||
name = "D2 na C"
|
||||
script = "ENTRY_backtest_strategyKOKA-ok"
|
||||
class = "StrategyOrderLimitKOKA"
|
||||
symbol = "C"
|
||||
open_rush = 0
|
||||
close_rush = 0
|
||||
|
||||
[strategies.stratvars]
|
||||
maxpozic = 200
|
||||
chunk = 10
|
||||
MA = 4
|
||||
Trend = 4
|
||||
profit = 0.01
|
||||
lastbuyindex=-6
|
||||
pendingbuys={}
|
||||
limitka = "None"
|
||||
|
||||
[[strategies.add_data]]
|
||||
symbol="C"
|
||||
rectype="bar"
|
||||
timeframe=10
|
||||
update_ltp=true
|
||||
align="round"
|
||||
mintick=0
|
||||
minsize=100
|
||||
exthours=false
|
||||
|
||||
#V3 na EPD
|
||||
[[strategies]]
|
||||
name = "V3 na EPD"
|
||||
symbol = "EPD"
|
||||
script = "ENTRY_backtest_strategyVykladaci"
|
||||
class = "StrategyOrderLimitVykladaci"
|
||||
open_rush = 0
|
||||
close_rush = 0
|
||||
|
||||
[strategies.stratvars]
|
||||
maxpozic = 200
|
||||
chunk = 10
|
||||
MA = 4
|
||||
Trend = 4
|
||||
profit = 0.02
|
||||
lastbuyindex=-6
|
||||
pendingbuys={}
|
||||
limitka = "None"
|
||||
jevylozeno=0
|
||||
vykladka=5
|
||||
curve = [0.01, 0.01, 0.01,0.01, 0.02, 0.01,0.01, 0.01,0.03, 0.01, 0.01, 0.01,0.04, 0.01,0.01, 0.01,0.05, 0.01,0.01, 0.01,0.01, 0.06,0.01, 0.01,0.01, 0.01]
|
||||
blockbuy = 0
|
||||
ticks2reset = 0.04
|
||||
|
||||
[[strategies.add_data]]
|
||||
symbol="EPD"
|
||||
rectype="bar"
|
||||
timeframe=15
|
||||
update_ltp=true
|
||||
align="round"
|
||||
mintick=0
|
||||
minsize=100
|
||||
exthours=false
|
||||
74
v2realbot/config.py
Normal file
74
v2realbot/config.py
Normal file
@ -0,0 +1,74 @@
|
||||
from alpaca.data.enums import DataFeed
|
||||
from v2realbot.enums.enums import Mode, Account
|
||||
from appdirs import user_data_dir
|
||||
|
||||
DATA_DIR = user_data_dir("v2realbot")
|
||||
#BT DELAYS
|
||||
|
||||
""""
|
||||
LATENCY DELAYS for LIVE eastcoast
|
||||
.000 trigger - last_trade_time (.4246266)
|
||||
+.020 vstup do strategie a BUY (.444606)
|
||||
+.023 submitted (.469198)
|
||||
+.008 filled (.476695552)
|
||||
+.023 fill not(.499888)
|
||||
"""
|
||||
#TODO změnit názvy delay promennych vystizneji a obecneji
|
||||
class BT_DELAYS:
|
||||
trigger_to_strat: float = 0.020
|
||||
strat_to_sub: float = 0.023
|
||||
sub_to_fill: float = 0.008
|
||||
fill_to_not: float = 0.023
|
||||
#doplnit dle live
|
||||
limit_order_offset: float = 0
|
||||
|
||||
class Keys:
|
||||
def __init__(self, api_key, secret_key, paper, feed) -> None:
|
||||
self.API_KEY = api_key
|
||||
self.SECRET_KEY = secret_key
|
||||
self.PAPER = paper
|
||||
self.FEED = feed
|
||||
|
||||
# podle modu (PAPER, LIVE) vrati objekt
|
||||
# obsahujici klice pro pripojeni k alpace
|
||||
def get_key(mode: Mode, account: Account):
|
||||
if mode not in [Mode.PAPER, Mode.LIVE]:
|
||||
print("has to be LIVE or PAPER only")
|
||||
return None
|
||||
dict = globals()
|
||||
try:
|
||||
API_KEY = dict[str.upper(str(account.value)) + "_" + str.upper(str(mode.value)) + "_API_KEY" ]
|
||||
SECRET_KEY = dict[str.upper(str(account.value)) + "_" + str.upper(str(mode.value)) + "_SECRET_KEY" ]
|
||||
PAPER = dict[str.upper(str(account.value)) + "_" + str.upper(str(mode.value)) + "_PAPER" ]
|
||||
FEED = dict[str.upper(str(account.value)) + "_" + str.upper(str(mode.value)) + "_FEED" ]
|
||||
return Keys(API_KEY, SECRET_KEY, PAPER, FEED)
|
||||
except KeyError:
|
||||
print("Not valid combination to get keys for", mode, account)
|
||||
return 0
|
||||
|
||||
#strategy instance main loop heartbeat
|
||||
HEARTBEAT_TIMEOUT=5
|
||||
|
||||
WEB_API_KEY="david"
|
||||
|
||||
#PRIMARY PAPER
|
||||
ACCOUNT1_PAPER_API_KEY = 'PKGGEWIEYZOVQFDRY70L'
|
||||
ACCOUNT1_PAPER_SECRET_KEY = 'O5Kt8X4RLceIOvM98i5LdbalItsX7hVZlbPYHy8Y'
|
||||
ACCOUNT1_PAPER_MAX_BATCH_SIZE = 1
|
||||
ACCOUNT1_PAPER_PAPER = True
|
||||
ACCOUNT1_PAPER_FEED = DataFeed.SIP
|
||||
|
||||
#PRIMARY LIVE
|
||||
ACCOUNT1_LIVE_API_KEY = 'AKB5HD32LPDZC9TPUWJT'
|
||||
ACCOUNT1_LIVE_SECRET_KEY = 'Xq1wPSNOtwmlMTAd4cEmdKvNDgfcUYfrOaCccaAs'
|
||||
ACCOUNT1_LIVE_MAX_BATCH_SIZE = 1
|
||||
ACCOUNT1_LIVE_PAPER = False
|
||||
ACCOUNT1_LIVE_FEED = DataFeed.SIP
|
||||
|
||||
#SECONDARY PAPER
|
||||
ACCOUNT2_PAPER_API_KEY = 'PKQEAAJTVC72SZO3CT3R'
|
||||
ACCOUNT2_PAPER_SECRET_KEY = 'mqdftzGJlJdvUjdsuQynAURCHRwAI0a8nhJy8nyz'
|
||||
ACCOUNT2_PAPER_MAX_BATCH_SIZE = 1
|
||||
ACCOUNT2_PAPER_PAPER = True
|
||||
ACCOUNT2_PAPER_FEED = DataFeed.IEX
|
||||
|
||||
0
v2realbot/controller/__init__.py
Normal file
0
v2realbot/controller/__init__.py
Normal file
BIN
v2realbot/controller/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
v2realbot/controller/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
v2realbot/controller/__pycache__/services.cpython-311.pyc
Normal file
BIN
v2realbot/controller/__pycache__/services.cpython-311.pyc
Normal file
Binary file not shown.
317
v2realbot/controller/services.py
Normal file
317
v2realbot/controller/services.py
Normal file
@ -0,0 +1,317 @@
|
||||
from typing import Any, List
|
||||
from uuid import UUID, uuid4
|
||||
import pickle
|
||||
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account
|
||||
from v2realbot.common.model import StrategyInstance, Runner, RunRequest
|
||||
from v2realbot.utils.utils import AttributeDict, zoneNY, dict_replace_value, Store, parse_toml_string
|
||||
from datetime import datetime
|
||||
from threading import Thread, current_thread, Event, enumerate
|
||||
import importlib
|
||||
from queue import Queue
|
||||
db = Store()
|
||||
|
||||
def get_all_threads():
|
||||
res = str(enumerate())
|
||||
if len(res) > 0:
|
||||
return (0, res)
|
||||
else:
|
||||
return (-2, "not found")
|
||||
|
||||
def get_all_runners():
|
||||
if len(db.runners) > 0:
|
||||
print(db.runners)
|
||||
return (0, db.runners)
|
||||
else:
|
||||
return (0, [])
|
||||
|
||||
def get_all_stratins():
|
||||
if len(db.stratins) > 0:
|
||||
return (0, db.stratins)
|
||||
else:
|
||||
return (0, [])
|
||||
|
||||
def get_stratin(id: UUID):
|
||||
for i in db.stratins:
|
||||
if str(i.id) == str(id):
|
||||
return (0, i)
|
||||
return (-2, "not found")
|
||||
|
||||
def get_runner(id: UUID):
|
||||
for i in db.runners:
|
||||
if str(i.id) == str(id):
|
||||
return (0, i)
|
||||
return (-2, "not found")
|
||||
|
||||
def create_stratin(si: StrategyInstance):
|
||||
#validate toml
|
||||
res, stp = parse_toml_string(si.stratvars_conf)
|
||||
if res < 0:
|
||||
return (-1,"stratvars invalid")
|
||||
res, adp = parse_toml_string(si.add_data_conf)
|
||||
if res < 0:
|
||||
return (-1, "None")
|
||||
si.id = uuid4()
|
||||
print(si)
|
||||
db.stratins.append(si)
|
||||
db.save()
|
||||
#print(db.stratins)
|
||||
return (0,si.id)
|
||||
|
||||
def modify_stratin(si: StrategyInstance, id: UUID):
|
||||
#validate toml if fields exists
|
||||
if is_stratin_running(id):
|
||||
return (-1, "strat is running, use modify_stratin_running")
|
||||
res, stp = parse_toml_string(si.stratvars_conf)
|
||||
if res < 0:
|
||||
return (-1, "stratvars invalid")
|
||||
res, adp = parse_toml_string(si.add_data_conf)
|
||||
if res < 0:
|
||||
return (-1, "add data conf invalid")
|
||||
for i in db.stratins:
|
||||
if str(i.id) == str(id):
|
||||
print("removing",i)
|
||||
db.stratins.remove(i)
|
||||
print("adding",si)
|
||||
db.stratins.append(si)
|
||||
print(db.stratins)
|
||||
db.save()
|
||||
return (0, i.id)
|
||||
return (-2, "not found")
|
||||
|
||||
def delete_stratin(id: UUID):
|
||||
if is_stratin_running(id=str(id)):
|
||||
return (-1, "Strategy Instance is running " + str(id))
|
||||
for i in db.stratins:
|
||||
if str(i.id) == str(id):
|
||||
db.stratins.remove(i)
|
||||
db.save()
|
||||
print(db.stratins)
|
||||
return (0, i.id)
|
||||
return (-2, "not found")
|
||||
|
||||
def inject_stratvars(id: UUID, stratvars_parsed: AttributeDict):
|
||||
for i in db.runners:
|
||||
if str(i.id) == str(id):
|
||||
i.run_instance.state.vars = AttributeDict(stratvars_parsed["stratvars"])
|
||||
i.run_instance.stratvars = AttributeDict(stratvars_parsed["stratvars"])
|
||||
return 0
|
||||
return -2
|
||||
|
||||
#allows change of set of parameters that are possible to change while it is running
|
||||
#also injects those parameters to instance
|
||||
def modify_stratin_running(si: StrategyInstance, id: UUID):
|
||||
#validate toml
|
||||
res,stp = parse_toml_string(si.stratvars_conf)
|
||||
if res < 0:
|
||||
return (-1, "stratvars invalid")
|
||||
for i in db.stratins:
|
||||
if str(i.id) == str(id):
|
||||
if not is_stratin_running(id=str(id)):
|
||||
return (-1, "not running")
|
||||
i.id2 = si.id2
|
||||
i.name = si.name
|
||||
i.open_rush = si.open_rush
|
||||
i.stratvars_conf = si.stratvars_conf
|
||||
i.note = si.note
|
||||
i.history = si.history
|
||||
db.save()
|
||||
#TODO reload running strat
|
||||
print(stp)
|
||||
print("starting injection", stp)
|
||||
res = inject_stratvars(id=si.id, stratvars_parsed=stp)
|
||||
if res < 0:
|
||||
print("ajajaj inject se nepovedl")
|
||||
return(-3, "inject failed")
|
||||
return (0, i.id)
|
||||
return (-2, "not found")
|
||||
#controller.reload_params(si)
|
||||
|
||||
##enable realtime chart - inject given queue for strategy instance
|
||||
##webservice listens to this queue
|
||||
async def stratin_realtime_on(id: UUID, rtqueue: Queue):
|
||||
for i in db.runners:
|
||||
if str(i.id) == str(id):
|
||||
i.run_instance.rtqueue = rtqueue
|
||||
print("RT QUEUE added")
|
||||
return 0
|
||||
print("ERROR NOT FOUND")
|
||||
return -2
|
||||
|
||||
async def stratin_realtime_off(id: UUID):
|
||||
for i in db.runners:
|
||||
if str(i.id) == str(id):
|
||||
i.run_instance.rtqueue = None
|
||||
print("RT QUEUE removed")
|
||||
return 0
|
||||
print("ERROR NOT FOUND")
|
||||
return -2
|
||||
|
||||
##controller (run_stratefy, pause, stop, reload_params)
|
||||
def pause_stratin(id: UUID):
|
||||
for i in db.runners:
|
||||
print(i.id)
|
||||
if str(i.id) == id:
|
||||
if i.run_pause_ev.is_set():
|
||||
i.run_pause_ev.clear()
|
||||
i.run_paused = None
|
||||
print("Unpaused")
|
||||
return (0, "unpaused runner " + str(i.id))
|
||||
print("pausing runner", i.id)
|
||||
i.run_pause_ev.set()
|
||||
i.run_paused = datetime.now().astimezone(zoneNY)
|
||||
return (0, "paused runner " + str(i.id))
|
||||
print("no ID found")
|
||||
return (-1, "not running instance found")
|
||||
|
||||
def stop_stratin(id: UUID = None):
|
||||
chng = []
|
||||
for i in db.runners:
|
||||
#print(i['id'])
|
||||
if id is None or str(i.id) == id:
|
||||
chng.append(i.id)
|
||||
print("Sending STOP signal to Runner", i.id)
|
||||
#just sending the signal, update is done in stop after plugin
|
||||
i.run_stop_ev.set()
|
||||
# i.run_stopped = datetime.now().astimezone(zoneNY)
|
||||
# i.run_thread = None
|
||||
# i.run_instance = None
|
||||
# i.run_pause_ev = None
|
||||
# i.run_stop_ev = None
|
||||
# #stratins.remove(i)
|
||||
if len(chng) > 0:
|
||||
return (0, "Sent STOP signal to those" + str(chng))
|
||||
else:
|
||||
return (-2, "not found" + str(id))
|
||||
|
||||
def is_stratin_running(id: UUID):
|
||||
for i in db.runners:
|
||||
if str(i.id) == str(id):
|
||||
if i.run_started is not None and i.run_stopped is None:
|
||||
return True
|
||||
return False
|
||||
|
||||
def save_history(id: UUID, st: object, runner: Runner, reason: str = None):
|
||||
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:XX" + reason + "<BR>"
|
||||
#i.history += str(runner.__dict__)+"<BR>"
|
||||
db.save()
|
||||
|
||||
#Capsule to run the thread in. Needed in order to update db after strat ends for any reason#
|
||||
def capsule(target: object, db: object):
|
||||
|
||||
#TODO zde odchytit pripadnou exceptionu a zapsat do history
|
||||
#cil aby padnuti jedne nezpusobilo pad enginu
|
||||
try:
|
||||
target.start()
|
||||
print("Strategy instance stopped. Update runners")
|
||||
reason = "SHUTDOWN OK"
|
||||
except Exception as e:
|
||||
reason = "SHUTDOWN Exception:" + str(e)
|
||||
finally:
|
||||
# remove runners after thread is stopped and save results to stratin history
|
||||
for i in db.runners:
|
||||
if i.run_instance == target:
|
||||
i.run_stopped = datetime.now().astimezone(zoneNY)
|
||||
i.run_thread = None
|
||||
i.run_instance = None
|
||||
i.run_pause_ev = None
|
||||
i.run_stop_ev = None
|
||||
#ukladame radek do historie (pozdeji refactor)
|
||||
save_history(id=i.id, st=target, runner=i, reason=reason)
|
||||
#mazeme runner po skonceni instance
|
||||
db.runners.remove(i)
|
||||
|
||||
print("Runner STOPPED")
|
||||
|
||||
def run_stratin(id: UUID, runReq: RunRequest):
|
||||
if runReq.mode == Mode.BT:
|
||||
if runReq.bt_from is None:
|
||||
return (-1, "start date required for BT")
|
||||
if runReq.bt_to is None:
|
||||
runReq.bt_to = datetime.now().astimezone(zoneNY)
|
||||
|
||||
print("hodnota ID pred",id)
|
||||
#volani funkce instantiate_strategy
|
||||
for i in db.stratins:
|
||||
if str(i.id) == str(id):
|
||||
try:
|
||||
if is_stratin_running(id=id):
|
||||
return(-1, "already running")
|
||||
#validate toml
|
||||
res, stp = parse_toml_string(i.stratvars_conf)
|
||||
if res < 0:
|
||||
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")
|
||||
id = uuid4()
|
||||
name = i.name
|
||||
symbol = i.symbol
|
||||
open_rush = i.open_rush
|
||||
close_rush = i.close_rush
|
||||
#do budoucna vylepsit konfiguraci, udelat na frontendu jedno pole config
|
||||
#obsahujici cely toml dane strategie
|
||||
#nyni predpokladame, ze stratvars a add_data sloupce v gui obsahuji
|
||||
#dany TOML element
|
||||
try:
|
||||
stratvars = AttributeDict(stp["stratvars"])
|
||||
except KeyError:
|
||||
return (-1, "stratvars musi obsahovat element [stratvars]")
|
||||
classname = i.class_name
|
||||
script = "v2realbot."+i.script
|
||||
pe = Event()
|
||||
se = Event()
|
||||
|
||||
import_script = importlib.import_module(script)
|
||||
next = getattr(import_script, "next")
|
||||
init = getattr(import_script, "init")
|
||||
my_module = importlib.import_module("v2realbot.strategy."+classname)
|
||||
StrategyClass = getattr(my_module, classname)
|
||||
#instance strategie
|
||||
instance = StrategyClass(name= name,
|
||||
symbol=symbol,
|
||||
account=runReq.account,
|
||||
next=next,
|
||||
init=init,
|
||||
stratvars=stratvars,
|
||||
open_rush=open_rush, close_rush=close_rush, pe=pe, se=se)
|
||||
print("instance vytvorena", instance)
|
||||
#set mode
|
||||
if runReq.mode == Mode.LIVE or runReq.mode == Mode.PAPER:
|
||||
instance.set_mode(mode=runReq.mode, debug = runReq.debug)
|
||||
else:
|
||||
instance.set_mode(mode = Mode.BT,
|
||||
debug = runReq.debug,
|
||||
start = runReq.bt_from.astimezone(zoneNY),
|
||||
end = runReq.bt_to.astimezone(zoneNY),
|
||||
cash=runReq.cash)
|
||||
##add data streams
|
||||
for st in adp["add_data"]:
|
||||
print("adding stream", st)
|
||||
instance.add_data(**st)
|
||||
|
||||
print("Starting strategy", instance.name)
|
||||
#vlakno = Thread(target=instance.start, name=instance.name)
|
||||
#pokus na spusteni v kapsli, abychom po skonceni mohli updatnout stratin
|
||||
vlakno = Thread(target=capsule, args=(instance,db), name=instance.name)
|
||||
vlakno.start()
|
||||
print("Spuštěna", instance.name)
|
||||
##storing the attributtes - pozor pri stopu je zase odstranit
|
||||
runner = Runner(id = i.id,
|
||||
run_started = datetime.now(zoneNY),
|
||||
run_pause_ev = pe,
|
||||
run_stop_ev = se,
|
||||
run_thread = vlakno,
|
||||
run_account = runReq.account,
|
||||
run_mode = runReq.mode,
|
||||
run_instance = instance)
|
||||
db.runners.append(runner)
|
||||
print(db.runners)
|
||||
print(i)
|
||||
print(enumerate())
|
||||
return (0, i.id)
|
||||
except Exception as e:
|
||||
return (-2, "Exception: "+str(e))
|
||||
return (-2, "not found")
|
||||
27
v2realbot/emptystrategy.py
Normal file
27
v2realbot/emptystrategy.py
Normal file
@ -0,0 +1,27 @@
|
||||
from strategy import MyStrategy, StrategyState
|
||||
from enums import RecordType, StartBarAlign
|
||||
from config import API_KEY, SECRET_KEY, MAX_BATCH_SIZE, PAPER
|
||||
from indicators import ema
|
||||
from rich import print
|
||||
|
||||
def next(data, state: StrategyState):
|
||||
print("next")
|
||||
print(state.variables.MA)
|
||||
print(state.variables.maxpozic)
|
||||
print(data)
|
||||
print(state.oe.pos())
|
||||
|
||||
def init(state: StrategyState):
|
||||
print("init - zatim bez data")
|
||||
print(state.oe.symbol)
|
||||
print(state.oe.pos())
|
||||
print()
|
||||
|
||||
def main():
|
||||
stratvars = dict(maxpozic = 10, chunk = 1, MA = 3, Trend = 4,profit = 0.01)
|
||||
s = MyStrategy("TSLA",paper=PAPER, next=next, init=init, stratvars=stratvars)
|
||||
s.add_data(symbol="TSLA",rectype=RecordType.TRADE,timeframe=5,filters=None,update_ltp=True,align=StartBarAlign.ROUND,mintick=0)
|
||||
s.start()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
0
v2realbot/enums/__init__.py
Normal file
0
v2realbot/enums/__init__.py
Normal file
BIN
v2realbot/enums/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
v2realbot/enums/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
v2realbot/enums/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
v2realbot/enums/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
v2realbot/enums/__pycache__/enums.cpython-310.pyc
Normal file
BIN
v2realbot/enums/__pycache__/enums.cpython-310.pyc
Normal file
Binary file not shown.
BIN
v2realbot/enums/__pycache__/enums.cpython-311.pyc
Normal file
BIN
v2realbot/enums/__pycache__/enums.cpython-311.pyc
Normal file
Binary file not shown.
BIN
v2realbot/enums/__pycache__/myenums.cpython-311.pyc
Normal file
BIN
v2realbot/enums/__pycache__/myenums.cpython-311.pyc
Normal file
Binary file not shown.
47
v2realbot/enums/enums.py
Normal file
47
v2realbot/enums/enums.py
Normal file
@ -0,0 +1,47 @@
|
||||
from enum import Enum
|
||||
from alpaca.trading.enums import OrderSide, OrderStatus
|
||||
class Order:
|
||||
def __init__(self, id: str, status: OrderStatus, side: OrderSide, symbol: str, qty: int, limit_price: float = None, filled_qty: int = 0, filled_avg_price: float = 0, filled_time: float = None) -> None:
|
||||
self.id = id
|
||||
self.status = status
|
||||
self.side = side
|
||||
self.symbol = symbol
|
||||
self.qty = qty
|
||||
self.filled_qty = filled_qty
|
||||
self.filled_avg_price = filled_avg_price
|
||||
self.filled_time = filled_time
|
||||
self.limit_price = limit_price
|
||||
|
||||
class Account(Enum):
|
||||
"""
|
||||
Accounts - keys to config
|
||||
"""
|
||||
ACCOUNT1 = "ACCOUNT1"
|
||||
ACCOUNT2 = "ACCOUNT2"
|
||||
class RecordType(Enum):
|
||||
"""
|
||||
Represents output of aggregator
|
||||
"""
|
||||
|
||||
BAR = "bar"
|
||||
CBAR = "continuosbar"
|
||||
TRADE = "trade"
|
||||
|
||||
class Mode(Enum):
|
||||
"""
|
||||
LIVE or BT
|
||||
"""
|
||||
|
||||
PAPER = "paper"
|
||||
LIVE = "live"
|
||||
BT = "backtest"
|
||||
|
||||
|
||||
class StartBarAlign(Enum):
|
||||
"""
|
||||
Represents first bar start time alignement according to timeframe
|
||||
ROUND = bar starts at 0,5,10 (for 5s timeframe)
|
||||
RANDOM = first bar starts when first trade occurs
|
||||
"""
|
||||
ROUND = "round"
|
||||
RANDOM = "random"
|
||||
0
v2realbot/indicators/__init__.py
Normal file
0
v2realbot/indicators/__init__.py
Normal file
BIN
v2realbot/indicators/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
v2realbot/indicators/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
v2realbot/indicators/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
v2realbot/indicators/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
v2realbot/indicators/__pycache__/indicators.cpython-310.pyc
Normal file
BIN
v2realbot/indicators/__pycache__/indicators.cpython-310.pyc
Normal file
Binary file not shown.
BIN
v2realbot/indicators/__pycache__/indicators.cpython-311.pyc
Normal file
BIN
v2realbot/indicators/__pycache__/indicators.cpython-311.pyc
Normal file
Binary file not shown.
36
v2realbot/indicators/indicators.py
Normal file
36
v2realbot/indicators/indicators.py
Normal file
@ -0,0 +1,36 @@
|
||||
import tulipy as ti
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from collections import deque
|
||||
import typing
|
||||
|
||||
def ema(data, period: int = 50, use_series=False):
|
||||
if check_series(data):
|
||||
use_series = True
|
||||
data = convert_to_numpy(data)
|
||||
ema = ti.ema(data, period=period)
|
||||
return pd.Series(ema) if use_series else ema
|
||||
|
||||
def sma(data, period: int = 50, use_series=False):
|
||||
"""
|
||||
Finding the moving average of a dataset
|
||||
Args:
|
||||
data: (list) A list containing the data you want to find the moving average of
|
||||
period: (int) How far each average set should be
|
||||
"""
|
||||
if check_series(data):
|
||||
use_series = True
|
||||
data = convert_to_numpy(data)
|
||||
sma = ti.sma(data, period=period)
|
||||
return pd.Series(sma) if use_series else sma
|
||||
|
||||
def convert_to_numpy(data):
|
||||
if isinstance(data, list) or isinstance(data, deque):
|
||||
return np.fromiter(data, float)
|
||||
elif isinstance(data, pd.Series):
|
||||
return data.to_numpy()
|
||||
return data
|
||||
|
||||
|
||||
def check_series(data):
|
||||
return isinstance(data, pd.Series)
|
||||
119
v2realbot/indicators/moving_averages.py
Normal file
119
v2realbot/indicators/moving_averages.py
Normal file
@ -0,0 +1,119 @@
|
||||
"""
|
||||
Moving average function utils
|
||||
Copyright (C) 2021 Brandon Fan
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
#inspirovat se, pripadne vyzkouset i TAlib
|
||||
|
||||
from typing import Any
|
||||
|
||||
import pandas as pd
|
||||
import tulipy as ti
|
||||
|
||||
def convert_to_numpy(data: Any):
|
||||
if isinstance(data, list) or isinstance(data, deque):
|
||||
return np.fromiter(data, float)
|
||||
elif isinstance(data, pd.Series):
|
||||
return data.to_numpy()
|
||||
return data
|
||||
|
||||
|
||||
def check_series(data: Any):
|
||||
return isinstance(data, pd.Series)
|
||||
|
||||
def ema(data: Any, period: int = 50, use_series=False) -> Any:
|
||||
if check_series(data):
|
||||
use_series = True
|
||||
data = convert_to_numpy(data)
|
||||
ema = ti.ema(data, period=period)
|
||||
return pd.Series(ema) if use_series else ema
|
||||
|
||||
|
||||
def vwma(data: Any, volume_data: Any, period: int = 50, use_series=False) -> Any:
|
||||
if check_series(data):
|
||||
use_series = True
|
||||
|
||||
data = convert_to_numpy(data)
|
||||
volume_data = convert_to_numpy(volume_data).astype(float)
|
||||
|
||||
vwma = ti.vwma(data, volume_data, period=period)
|
||||
return pd.Series(vwma) if use_series else vwma
|
||||
|
||||
|
||||
def wma(data: Any, period: int = 50, use_series=False) -> Any:
|
||||
if check_series(data):
|
||||
use_series = True
|
||||
data = convert_to_numpy(data)
|
||||
wma = ti.wma(data, period)
|
||||
return pd.Series(wma) if use_series else wma
|
||||
|
||||
|
||||
def zlema(data: Any, period: int = 50, use_series=False) -> Any:
|
||||
if check_series(data):
|
||||
use_series = True
|
||||
data = convert_to_numpy(data)
|
||||
zlema = ti.zlema(data, period)
|
||||
return pd.Series(zlema) if use_series else zlema
|
||||
|
||||
|
||||
def sma(data: Any, period: int = 50, use_series=False) -> Any:
|
||||
"""
|
||||
Finding the moving average of a dataset
|
||||
Args:
|
||||
data: (list) A list containing the data you want to find the moving average of
|
||||
period: (int) How far each average set should be
|
||||
"""
|
||||
if check_series(data):
|
||||
use_series = True
|
||||
data = convert_to_numpy(data)
|
||||
sma = ti.sma(data, period=period)
|
||||
return pd.Series(sma) if use_series else sma
|
||||
|
||||
|
||||
def hma(data: Any, period: int = 50, use_series=False) -> Any:
|
||||
if check_series(data):
|
||||
use_series = True
|
||||
data = convert_to_numpy(data)
|
||||
hma = ti.hma(data, period)
|
||||
return pd.Series(hma) if use_series else hma
|
||||
|
||||
|
||||
def kaufman_adaptive_ma(data: Any, period: int = 50, use_series=False) -> Any:
|
||||
if check_series(data):
|
||||
use_series = True
|
||||
data = convert_to_numpy(data)
|
||||
kama = ti.kama(data, period)
|
||||
return pd.Series(kama) if use_series else kama
|
||||
|
||||
|
||||
def trima(data: Any, period: int = 50, use_series=False) -> Any:
|
||||
if check_series(data):
|
||||
use_series = True
|
||||
data = convert_to_numpy(data)
|
||||
trima = ti.trima(data, period)
|
||||
return pd.Series(trima) if use_series else trima
|
||||
|
||||
|
||||
def macd(data: Any, short_period: int = 12, long_period: int = 26, signal_period: int = 9, use_series=False) -> Any:
|
||||
if check_series(data):
|
||||
use_series = True
|
||||
data = convert_to_numpy(data)
|
||||
macd, macd_signal, macd_histogram = ti.macd(data, short_period, long_period, signal_period)
|
||||
if use_series:
|
||||
df = pd.DataFrame({'macd': macd, 'macd_signal': macd_signal, 'macd_histogram': macd_histogram})
|
||||
return df
|
||||
return macd, macd_signal, macd_histogram
|
||||
0
v2realbot/interfaces/__init__.py
Normal file
0
v2realbot/interfaces/__init__.py
Normal file
BIN
v2realbot/interfaces/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
v2realbot/interfaces/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
v2realbot/interfaces/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
v2realbot/interfaces/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
v2realbot/interfaces/__pycache__/live_interface.cpython-310.pyc
Normal file
BIN
v2realbot/interfaces/__pycache__/live_interface.cpython-310.pyc
Normal file
Binary file not shown.
BIN
v2realbot/interfaces/__pycache__/live_interface.cpython-311.pyc
Normal file
BIN
v2realbot/interfaces/__pycache__/live_interface.cpython-311.pyc
Normal file
Binary file not shown.
BIN
v2realbot/interfaces/__pycache__/orders.cpython-311.pyc
Normal file
BIN
v2realbot/interfaces/__pycache__/orders.cpython-311.pyc
Normal file
Binary file not shown.
BIN
v2realbot/interfaces/__pycache__/orderupdates.cpython-311.pyc
Normal file
BIN
v2realbot/interfaces/__pycache__/orderupdates.cpython-311.pyc
Normal file
Binary file not shown.
67
v2realbot/interfaces/backtest_interface.py
Normal file
67
v2realbot/interfaces/backtest_interface.py
Normal file
@ -0,0 +1,67 @@
|
||||
from alpaca.trading.enums import OrderSide, OrderType
|
||||
from threading import Lock
|
||||
from v2realbot.interfaces.general_interface import GeneralInterface
|
||||
from v2realbot.backtesting.backtester import Backtester
|
||||
from v2realbot.config import BT_DELAYS
|
||||
|
||||
""""
|
||||
backtester methods can be called
|
||||
- within the Strategy
|
||||
- from the OrderUpdate notification callback
|
||||
|
||||
both should be backtestable
|
||||
|
||||
if method are called for the past self.time must be set accordingly
|
||||
"""
|
||||
class BacktestInterface(GeneralInterface):
|
||||
def __init__(self, symbol, bt: Backtester) -> None:
|
||||
self.symbol = symbol
|
||||
self.bt = bt
|
||||
#TODO time v API nejspis muzeme dat pryc a BT bude si to brat primo ze self.time (nezapomenout na + BT_DELAYS)
|
||||
# self.time = self.bt.time
|
||||
|
||||
"""initial checks."""
|
||||
def start_checks(self):
|
||||
print("start_checks")
|
||||
|
||||
"""buy market"""
|
||||
def buy(self, size = 1, repeat: bool = False):
|
||||
#add REST API latency
|
||||
return self.bt.submit_order(time=self.bt.time + BT_DELAYS.strat_to_sub,symbol=self.symbol,side=OrderSide.BUY,size=size,order_type = OrderType.MARKET)
|
||||
|
||||
"""buy limit"""
|
||||
def buy_l(self, price: float, size: int = 1, repeat: bool = False, force: int = 0):
|
||||
return self.bt.submit_order(time=self.bt.time + BT_DELAYS.strat_to_sub,symbol=self.symbol,side=OrderSide.BUY,size=size,price=price,order_type = OrderType.LIMIT)
|
||||
|
||||
"""sell market"""
|
||||
def sell(self, size = 1, repeat: bool = False):
|
||||
return self.bt.submit_order(time=self.bt.time + BT_DELAYS.strat_to_sub,symbol=self.symbol,side=OrderSide.SELL,size=size,order_type = OrderType.MARKET)
|
||||
|
||||
"""sell limit"""
|
||||
async def sell_l(self, price: float, size = 1, repeat: bool = False):
|
||||
return self.bt.submit_order(time=self.bt.time + BT_DELAYS.strat_to_sub,symbol=self.symbol,side=OrderSide.SELL,size=size,price=price,order_type = OrderType.LIMIT)
|
||||
|
||||
"""replace order"""
|
||||
async def repl(self, orderid: str, price: float = None, size: int = None, repeat: bool = False):
|
||||
return self.bt.replace_order(time=self.bt.time + BT_DELAYS.strat_to_sub,id=orderid,size=size,price=price)
|
||||
|
||||
"""cancel order"""
|
||||
#TBD exec predtim?
|
||||
def cancel(self, orderid: str):
|
||||
return self.bt.cancel_order(time=self.bt.time + BT_DELAYS.strat_to_sub, id=orderid)
|
||||
|
||||
"""get positions ->(size,avgp)"""
|
||||
#TBD exec predtim?
|
||||
def pos(self):
|
||||
return self.bt.get_open_position(symbol=self.symbol)
|
||||
|
||||
"""get open orders ->list(Order)"""
|
||||
def get_open_orders(self, side: OrderSide, symbol: str):
|
||||
return self.bt.get_open_orders(side=side, symbol=symbol)
|
||||
|
||||
def get_last_price(self, symbol: str):
|
||||
return self.bt.get_last_price(time=self.bt.time)
|
||||
|
||||
|
||||
|
||||
|
||||
42
v2realbot/interfaces/general_interface.py
Normal file
42
v2realbot/interfaces/general_interface.py
Normal file
@ -0,0 +1,42 @@
|
||||
from alpaca.trading.enums import OrderSide
|
||||
|
||||
class GeneralInterface:
|
||||
"""initial checks."""
|
||||
def start_checks(self):
|
||||
pass
|
||||
|
||||
"""buy market"""
|
||||
def buy(self, size = 1, repeat: bool = False):
|
||||
pass
|
||||
|
||||
"""buy limit"""
|
||||
def buy_l(self, price: float, size: int = 1, repeat: bool = False, force: int = 0):
|
||||
pass
|
||||
|
||||
"""sell market"""
|
||||
async def sell(self, size = 1, repeat: bool = False):
|
||||
pass
|
||||
|
||||
"""sell limit"""
|
||||
async def sell_l(self, price: float, size = 1, repeat: bool = False):
|
||||
pass
|
||||
|
||||
"""order replace"""
|
||||
async def repl(self, price: float, orderid: str, size: int = 1, repeatl: bool = False):
|
||||
pass
|
||||
|
||||
"""order update callback"""
|
||||
async def orderUpdate(self, data):
|
||||
pass
|
||||
|
||||
"""get open positions"""
|
||||
def pos(self) -> tuple[int, int]:
|
||||
pass
|
||||
|
||||
"""get open orders"""
|
||||
def get_open_orders(self, side: OrderSide, symbol: str):
|
||||
pass
|
||||
|
||||
"""get most recent price"""
|
||||
def get_last_price(symbol: str):
|
||||
pass
|
||||
187
v2realbot/interfaces/live_interface.py
Normal file
187
v2realbot/interfaces/live_interface.py
Normal file
@ -0,0 +1,187 @@
|
||||
from v2realbot.enums.enums import RecordType, StartBarAlign
|
||||
from datetime import datetime, timedelta
|
||||
from v2realbot.utils.utils import ltp
|
||||
from alpaca.trading.client import TradingClient
|
||||
from alpaca.trading.requests import MarketOrderRequest, TakeProfitRequest, LimitOrderRequest, ReplaceOrderRequest, GetOrdersRequest
|
||||
from alpaca.trading.enums import OrderSide, TimeInForce, OrderClass, OrderStatus, QueryOrderStatus
|
||||
from alpaca.trading.models import Order, Position
|
||||
from alpaca.common.exceptions import APIError
|
||||
from v2realbot.config import Keys
|
||||
from interfaces.general_interface import GeneralInterface
|
||||
"""""
|
||||
Live interface with Alpaca for LIVE and PAPER trading.
|
||||
"""""
|
||||
class LiveInterface(GeneralInterface):
|
||||
def __init__(self, symbol: str, key: Keys) -> None:
|
||||
self.symbol = symbol
|
||||
self.key :Keys = key
|
||||
self.trading_client = TradingClient(api_key=key.API_KEY, secret_key=key.SECRET_KEY, paper=key.PAPER)
|
||||
|
||||
def start_checks(self):
|
||||
pass
|
||||
|
||||
"""buy market"""
|
||||
def buy(self, size = 1, repeat: bool = False):
|
||||
|
||||
order_request = MarketOrderRequest(
|
||||
symbol=self.symbol,
|
||||
qty=size,
|
||||
side=OrderSide.BUY,
|
||||
time_in_force=TimeInForce.GTC,
|
||||
order_class=OrderClass.SIMPLE,
|
||||
take_profit = None
|
||||
)
|
||||
try:
|
||||
# Market order submit
|
||||
market_order = self.trading_client.submit_order(
|
||||
order_data=order_request
|
||||
)
|
||||
|
||||
return market_order.id
|
||||
except Exception as e:
|
||||
print("Nepodarilo se odeslat ", str(e))
|
||||
return -1
|
||||
|
||||
"""buy limit"""
|
||||
def buy_l(self, price: float, size: int = 1, repeat: bool = False, force: int = 0):
|
||||
|
||||
limit_request = LimitOrderRequest(
|
||||
symbol=self.symbol,
|
||||
qty=size,
|
||||
side=OrderSide.BUY,
|
||||
time_in_force=TimeInForce.GTC,
|
||||
order_class=OrderClass.SIMPLE,
|
||||
take_profit = None,
|
||||
limit_price = price
|
||||
)
|
||||
try:
|
||||
# Market order submit
|
||||
limit_order = self.trading_client.submit_order(
|
||||
order_data=limit_request
|
||||
)
|
||||
|
||||
print("LIIF: odeslana litmka s cenou", price, "- akt. cena", ltp.price[self.symbol])
|
||||
|
||||
return limit_order.id
|
||||
except Exception as e:
|
||||
print("Nepodarilo se odeslat limitku", str(e))
|
||||
return -1
|
||||
|
||||
"""sell market"""
|
||||
def sell(self, size = 1, repeat: bool = False):
|
||||
|
||||
order_request = MarketOrderRequest(
|
||||
symbol=self.symbol,
|
||||
qty=size,
|
||||
side=OrderSide.SELL,
|
||||
time_in_force=TimeInForce.GTC,
|
||||
order_class=OrderClass.SIMPLE,
|
||||
take_profit = None
|
||||
)
|
||||
try:
|
||||
# Market order submit
|
||||
market_order = self.trading_client.submit_order(
|
||||
order_data=order_request
|
||||
)
|
||||
|
||||
return market_order.id
|
||||
except Exception as e:
|
||||
print("Nepodarilo se odeslat ", str(e))
|
||||
return -1
|
||||
|
||||
"""sell limit"""
|
||||
async def sell_l(self, price: float, size = 1, repeat: bool = False):
|
||||
self.size = size
|
||||
self.repeat = repeat
|
||||
|
||||
limit_order = LimitOrderRequest(
|
||||
symbol=self.symbol,
|
||||
qty=self.size,
|
||||
side=OrderSide.SELL,
|
||||
time_in_force=TimeInForce.GTC,
|
||||
order_class=OrderClass.SIMPLE,
|
||||
limit_price = price)
|
||||
try:
|
||||
# Market order submit
|
||||
limit_order = self.trading_client.submit_order(
|
||||
order_data=limit_order
|
||||
)
|
||||
|
||||
#pripadne ulozeni do self.lastOrder
|
||||
return limit_order.id
|
||||
|
||||
except Exception as e:
|
||||
print("Nepodarilo se odeslat ", str(e))
|
||||
#raise Exception(e)
|
||||
return -1
|
||||
|
||||
"""order replace"""
|
||||
async def repl(self, orderid: str, price: float = None, size: int = None, repeatl: bool = False):
|
||||
|
||||
if not price and not size:
|
||||
print("price or size has to be filled")
|
||||
return -1
|
||||
|
||||
replace_request = ReplaceOrderRequest(qty=size, limit_price=price)
|
||||
|
||||
try:
|
||||
print("request na replace",replace_request)
|
||||
print("cislo objednavky",orderid)
|
||||
replaced_order = self.trading_client.replace_order_by_id(orderid, replace_request)
|
||||
print("replaced ok",replaced_order.id)
|
||||
return replaced_order.id
|
||||
except APIError as e:
|
||||
#stejne parametry - stava se pri rychle obratce, nevadi, vracime stejne orderid, chytne se dal
|
||||
if e.code == 42210000: return orderid
|
||||
else:
|
||||
##mozna tady proste vracet vzdy ok
|
||||
print("Neslo nahradit profitku. Problem",str(e))
|
||||
return -1
|
||||
#raise Exception(e)
|
||||
|
||||
"""order cancel"""
|
||||
def cancel(self, orderid: str):
|
||||
try:
|
||||
a = self.trading_client.cancel_order_by_id(orderid)
|
||||
print("rusime order",orderid)
|
||||
return a
|
||||
except APIError as e:
|
||||
#order doesnt exist
|
||||
if e.code == 40410000: return 0,0
|
||||
else:
|
||||
print("nepovedlo se zrusit objednavku")
|
||||
#raise Exception(e)
|
||||
return -1
|
||||
|
||||
"""get positions ->(size,avgp)"""
|
||||
def pos(self):
|
||||
try:
|
||||
a : Position = self.trading_client.get_open_position(self.symbol)
|
||||
self.avgp, self.poz = float(a.avg_entry_price), int(a.qty)
|
||||
return a.avg_entry_price, a.qty
|
||||
except APIError as e:
|
||||
#no position
|
||||
if e.code == 40410000: return 0,0
|
||||
else:
|
||||
#raise Exception(e)
|
||||
return -1
|
||||
|
||||
"""get open orders ->list(Order)"""
|
||||
def get_open_orders(self, side: OrderSide, symbol: str): # -> list(Order):
|
||||
getRequest = GetOrdersRequest(status=QueryOrderStatus.OPEN, side=OrderSide.SELL, symbols=[symbol])
|
||||
try:
|
||||
# Market order submit
|
||||
orderlist = self.trading_client.get_orders(getRequest)
|
||||
#list of Orders (orderlist[0].id)
|
||||
return orderlist
|
||||
except Exception as e:
|
||||
print("Chyba pri dotazeni objednávek.", str(e))
|
||||
#raise Exception (e)
|
||||
return -1
|
||||
|
||||
def get_last_price(self, symbol: str):
|
||||
return ltp.price[symbol]
|
||||
|
||||
|
||||
|
||||
|
||||
0
v2realbot/loader/__init__.py
Normal file
0
v2realbot/loader/__init__.py
Normal file
BIN
v2realbot/loader/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
v2realbot/loader/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
v2realbot/loader/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
v2realbot/loader/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
v2realbot/loader/__pycache__/aggregator.cpython-310.pyc
Normal file
BIN
v2realbot/loader/__pycache__/aggregator.cpython-310.pyc
Normal file
Binary file not shown.
BIN
v2realbot/loader/__pycache__/aggregator.cpython-311.pyc
Normal file
BIN
v2realbot/loader/__pycache__/aggregator.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
v2realbot/loader/__pycache__/trade_ws_streamer.cpython-310.pyc
Normal file
BIN
v2realbot/loader/__pycache__/trade_ws_streamer.cpython-310.pyc
Normal file
Binary file not shown.
BIN
v2realbot/loader/__pycache__/trade_ws_streamer.cpython-311.pyc
Normal file
BIN
v2realbot/loader/__pycache__/trade_ws_streamer.cpython-311.pyc
Normal file
Binary file not shown.
329
v2realbot/loader/aggregator.py
Normal file
329
v2realbot/loader/aggregator.py
Normal file
@ -0,0 +1,329 @@
|
||||
"""
|
||||
Aggregator mdoule containing main aggregator logic for TRADES, BARS and CBAR
|
||||
"""
|
||||
from v2realbot.enums.enums import RecordType, StartBarAlign
|
||||
from datetime import datetime, timedelta
|
||||
from v2realbot.utils.utils import parse_alpaca_timestamp, ltp, Queue,is_open_hours,zoneNY
|
||||
from queue import Queue
|
||||
from rich import print
|
||||
from v2realbot.enums.enums import Mode
|
||||
import threading
|
||||
from copy import deepcopy
|
||||
from msgpack import unpackb
|
||||
import os
|
||||
from config import DATA_DIR
|
||||
|
||||
class TradeAggregator:
|
||||
def __init__(self,
|
||||
rectype: RecordType = RecordType.BAR,
|
||||
timeframe: int = 5,
|
||||
minsize: int = 100,
|
||||
update_ltp: bool = False,
|
||||
align: StartBarAlign = StartBarAlign.ROUND,
|
||||
mintick: int = 0,
|
||||
exthours: bool = False):
|
||||
"""
|
||||
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
|
||||
CBar - returns continuous bar, finished bar is marked by confirmed status
|
||||
Args:
|
||||
timeframe (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
|
||||
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.minsize = minsize
|
||||
self.update_ltp = update_ltp
|
||||
self.exthours = exthours
|
||||
|
||||
if mintick >= timeframe:
|
||||
print("Mintick musi byt mensi nez timeframe")
|
||||
raise Exception
|
||||
|
||||
self.mintick = mintick
|
||||
#class variables = starters
|
||||
self.iterace = 1
|
||||
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.bar_start = 0
|
||||
self.align = align
|
||||
self.tm: datetime = None
|
||||
self.firstpass = True
|
||||
self.vwaphelper = 0
|
||||
self.returnBar = {}
|
||||
self.lastBarConfirmed = False
|
||||
#min trade size
|
||||
self.minsize = minsize
|
||||
|
||||
#instance variable to hold last trade price
|
||||
self.last_price = 0
|
||||
self.barindex = 1
|
||||
|
||||
async def ingest_trade(self, indata, symbol):
|
||||
"""
|
||||
Aggregator logic for trade record
|
||||
Args:
|
||||
indata (dict): online or offline record
|
||||
"""
|
||||
data = unpackb(indata)
|
||||
|
||||
#last item signal
|
||||
if data == "last": return data
|
||||
|
||||
#print(data)
|
||||
##implementing fitlers - zatim natvrdo a jen tyto: size: 1, cond in [O,C,4] opening,closed a derivately priced,
|
||||
## 22.3. - dal jsem pryc i contingency trades [' ', '7', 'V'] - nasel jsem obchod o 30c mimo
|
||||
## Q - jsou v pohode, oteviraci trady, ale O jsou jejich duplikaty
|
||||
try:
|
||||
for i in data['c']:
|
||||
if i in ('C','O','4','B','7','V'): return 0
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
#EXPERIMENT zkusime vyhodit vsechny pod 50 #puv if int(data['s']) == 1: return 0
|
||||
#zatim nechavame - výsledek je naprosto stejný jako v tradingview
|
||||
if int(data['s']) < self.minsize: return 0
|
||||
#{'t': 1678982075.242897, 'x': 'D', 'p': 29.1333, 's': 18000, 'c': [' ', '7', 'V'], 'i': 79372107591749, 'z': 'A', 'u': 'incorrect'}
|
||||
if 'u' in data: return 0
|
||||
|
||||
#pokud projde TRADE s cenou 0.33% rozdilna oproti predchozi, pak vyhazujeme v ramci cisteni dat (cca 10ticku na 30USD)
|
||||
pct_off = 0.33
|
||||
#ic(ltp.price)
|
||||
#ic(ltp.price[symbol])
|
||||
|
||||
try:
|
||||
ltp.price[symbol]
|
||||
except KeyError:
|
||||
ltp.price[symbol]=data['p']
|
||||
|
||||
|
||||
if float(data['p']) > float(ltp.price[symbol]) + (float(data['p'])/100*pct_off) or float(data['p']) < float(ltp.price[symbol])-(float(data['p'])/100*pct_off):
|
||||
print("ZLO", data,ltp.price[symbol])
|
||||
#nechavame zlo zatim projit
|
||||
##return 0
|
||||
# with open("cache/wrongtrades.txt", 'a') as fp:
|
||||
# fp.write(str(data) + 'predchozi:'+str(ltp.price[symbol])+'\n')
|
||||
|
||||
#timestampy jsou v UTC
|
||||
#TIMESTAMP format is different for online and offline trade streams
|
||||
#offline trade
|
||||
#{'t': '2023-02-17T14:30:00.16111744Z', 'x': 'J', 'p': 35.14, 's': 20, 'c': [' ', 'F', 'I'], 'i': 52983525027938, 'z': 'A'}
|
||||
#websocket trade
|
||||
#{'T': 't', 'S': 'MSFT', 'i': 372, 'x': 'V', 'p': 264.58, 's': 25, 'c': ['@', 'I'], 'z': 'C', 't': Timestamp(seconds=1678973696, nanoseconds=67312449), 'r': Timestamp(seconds=1678973696, nanoseconds=72865209)}
|
||||
#parse alpaca timestamp
|
||||
|
||||
# tzn. na offline mohu pouzit >>> datetime.fromisoformat(d).timestamp() 1676644200.161117
|
||||
#orizne sice nanosekundy ale to nevadi
|
||||
#print("tady", self.mode, data['t'])
|
||||
# if self.mode == Mode.BT:
|
||||
# data['t'] = datetime.fromisoformat(str(data['t'])).timestamp()
|
||||
# else:
|
||||
data['t'] = parse_alpaca_timestamp(data['t'])
|
||||
|
||||
if not is_open_hours(datetime.fromtimestamp(data['t'])) and self.exthours is False:
|
||||
#print("AGG: trade not in open hours skipping", datetime.fromtimestamp(data['t']).astimezone(zoneNY))
|
||||
return 0
|
||||
|
||||
#tady bude vzdycky posledni cena a posledni cas
|
||||
if self.update_ltp:
|
||||
ltp.price[symbol] = data['p']
|
||||
ltp.time[symbol] = data['t']
|
||||
|
||||
#if data['p'] < self.last_price - 0.02: print("zlo:",data)
|
||||
|
||||
if self.rectype == RecordType.TRADE: return data
|
||||
|
||||
#print("agr přišel trade", datetime.fromtimestamp(data['t']),data)
|
||||
|
||||
#OPIC pokud bude vadit, ze prvni bar neni kompletni - pak zapnout tuto opicarnu
|
||||
#kddyz jde o prvni iteraci a pozadujeme align, cekame na kulaty cas (pro 5s 0,5,10..)
|
||||
# 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.firstpass = False
|
||||
# print("trade: ",datetime.fromtimestamp(data['t']))
|
||||
# print("required",self.tm)
|
||||
# if self.tm > datetime.fromtimestamp(data['t']):
|
||||
# return
|
||||
# else: pass
|
||||
|
||||
#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:
|
||||
issamebar = True
|
||||
else:
|
||||
issamebar = False
|
||||
##flush předchozí bar a incializace (krom prvni iterace)
|
||||
if self.lasttimestamp ==0: pass
|
||||
else:
|
||||
self.newBar['confirmed'] = 1
|
||||
self.newBar['vwap'] = self.vwaphelper / self.newBar['volume']
|
||||
#updatujeme čas - obsahuje datum tradu, který confirm triggeroval
|
||||
self.newBar['updated'] = data['t']
|
||||
|
||||
#ulozime datum akt.tradu pro mintick
|
||||
self.lastBarConfirmed = True
|
||||
#ukládám si předchozí (confirmed)bar k vrácení
|
||||
self.returnBar = self.newBar
|
||||
#print(self.returnBar)
|
||||
|
||||
#inicializuji pro nový bar
|
||||
self.vwaphelper = 0
|
||||
|
||||
# return self.newBar
|
||||
##flush CONFIRMED bar to queue
|
||||
#self.q.put(self.newBar)
|
||||
##TODO pridat prubezne odesilani pokud je pozadovano
|
||||
self.barindex +=1
|
||||
self.newBar = {
|
||||
"close": 0,
|
||||
"high": 0,
|
||||
"low": 99999999,
|
||||
"volume": 0,
|
||||
"trades": 0,
|
||||
"hlcc4": 0,
|
||||
"confirmed": 0,
|
||||
"updated": 0,
|
||||
"vwap": 0,
|
||||
"index": self.barindex
|
||||
}
|
||||
|
||||
self.last_price = data['p']
|
||||
|
||||
#spočteme vwap - potřebujeme předchozí hodnoty
|
||||
self.vwaphelper += (data['p'] * data['s'])
|
||||
self.newBar['updated'] = data['t']
|
||||
self.newBar['close'] = data['p']
|
||||
self.newBar['high'] = max(self.newBar['high'],data['p'])
|
||||
self.newBar['low'] = min(self.newBar['low'],data['p'])
|
||||
self.newBar['volume'] = self.newBar['volume'] + data['s']
|
||||
self.newBar['trades'] = self.newBar['trades'] + 1
|
||||
#pohrat si s timto round
|
||||
self.newBar['hlcc4'] = round((self.newBar['high']+self.newBar['low']+self.newBar['close']+self.newBar['close'])/4,3)
|
||||
|
||||
#predchozi bar byl v jine vterine, tzn. ukladame do noveho (aktualniho) pocatecni hodnoty
|
||||
if (issamebar == False):
|
||||
#zaciname novy bar
|
||||
|
||||
self.newBar['open'] = data['p']
|
||||
|
||||
#zarovname time prvniho baru podle timeframu kam patří (např. 5, 10, 15 ...) (ROUND)
|
||||
if self.align:
|
||||
t = datetime.fromtimestamp(data['t'])
|
||||
t = t - timedelta(seconds=t.second % self.timeframe,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)
|
||||
t = datetime.fromtimestamp(int(data['t']))
|
||||
#timestamp
|
||||
self.bar_start = int(data['t'])
|
||||
|
||||
|
||||
|
||||
|
||||
self.newBar['time'] = t
|
||||
self.newBar['resolution'] = self.timeframe
|
||||
self.newBar['confirmed'] = 0
|
||||
|
||||
|
||||
#uložíme do předchozí hodnoty (poznáme tak open a close)
|
||||
self.lasttimestamp = data['t']
|
||||
self.iterace += 1
|
||||
# print(self.iterace, data)
|
||||
|
||||
#je tu maly bug pro CBAR - kdy prvni trade, který potvrzuje predchozi bar
|
||||
#odesle potvrzeni predchoziho baru a nikoliv open stávajícího, ten posle až druhý trade
|
||||
#což asi nevadí
|
||||
|
||||
|
||||
#pokud je pripraveny, vracíme předchozí confirmed bar
|
||||
if len(self.returnBar) > 0:
|
||||
self.tmp = self.returnBar
|
||||
self.returnBar = {}
|
||||
#print(self.tmp)
|
||||
return self.tmp
|
||||
|
||||
#pro cont bar posilame ihned (TBD vwap a min bar tick value)
|
||||
if self.rectype == RecordType.CBAR:
|
||||
|
||||
#pokud je mintick nastavený a předchozí bar byl potvrzený
|
||||
if self.mintick != 0 and self.lastBarConfirmed:
|
||||
#d zacatku noveho baru musi ubehnout x sekund nez posilame updazte
|
||||
#pocatek noveho baru + Xs musi byt vetsi nez aktualni trade
|
||||
if (self.newBar['time'] + timedelta(seconds=self.mintick)) > datetime.fromtimestamp(data['t']):
|
||||
#print("waiting for mintick")
|
||||
return 0
|
||||
else:
|
||||
self.lastBarConfirmed = False
|
||||
|
||||
#doplnime prubezny vwap
|
||||
self.newBar['vwap'] = self.vwaphelper / self.newBar['volume']
|
||||
#print(self.newBar)
|
||||
return self.newBar
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
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)
|
||||
self.queue = queue
|
||||
self.symbol = symbol
|
||||
|
||||
async def ingest_trade(self, data):
|
||||
#print("ingest ve threadu:",current_thread().name)
|
||||
res = await super().ingest_trade(data, self.symbol)
|
||||
if res != 0:
|
||||
#print(res)
|
||||
#pri rychlem plneni vetsiho dictionary se prepisovali - vyreseno kopií
|
||||
if isinstance(res, dict):
|
||||
copy = res.copy()
|
||||
else:
|
||||
copy = res
|
||||
self.queue.put(copy)
|
||||
res = {}
|
||||
#print("po insertu",res)
|
||||
|
||||
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)
|
||||
self.btdata = btdata
|
||||
self.symbol = symbol
|
||||
# self.debugfile = DATA_DIR + "/BACprices.txt"
|
||||
# if os.path.exists(self.debugfile):
|
||||
# os.remove(self.debugfile)
|
||||
|
||||
async def ingest_trade(self, data):
|
||||
#print("ted vstoupil do tradeagg2list ingestu")
|
||||
res1 = await super().ingest_trade(data, self.symbol)
|
||||
#print("ted je po zpracovani", res1)
|
||||
if res1 != 0:
|
||||
#pri rychlem plneni vetsiho dictionary se prepisovali - vyreseno kopií
|
||||
if isinstance(res1, dict):
|
||||
copy = res1.copy()
|
||||
else:
|
||||
copy = res1
|
||||
if res1 == 'last': return 0
|
||||
self.btdata.append((copy['t'],copy['p']))
|
||||
# with open(self.debugfile, "a") as output:
|
||||
# output.write(str(copy['t']) + ' ' + str(datetime.fromtimestamp(copy['t']).astimezone(zoneNY)) + ' ' + str(copy['p']) + '\n')
|
||||
res1 = {}
|
||||
#print("po insertu",res)
|
||||
|
||||
|
||||
|
||||
44
v2realbot/loader/order_updates_streamer.py
Normal file
44
v2realbot/loader/order_updates_streamer.py
Normal file
@ -0,0 +1,44 @@
|
||||
from threading import Thread
|
||||
from alpaca.trading.stream import TradingStream
|
||||
from v2realbot.config import Keys
|
||||
|
||||
#jelikoz Alpaca podporuje pripojeni libovolneho poctu websocket instanci na order updates
|
||||
#vytvorime pro kazdou bezici instanci vlastni webservisu (jinak bychom museli delat instanci pro kombinaci ACCOUNT1 - LIVE, ACCOUNT1 - PAPER, ACCOUNT2 - PAPER ..)
|
||||
#bude jednodussi mit jednu instanci pokazde
|
||||
"""""
|
||||
Connects to Alpaca websocket, listens to trade updates
|
||||
of given account. All notifications of given SYMBOL
|
||||
routes to strategy callback.
|
||||
|
||||
As Alpaca supports connecting of any number of trade updates clients
|
||||
new instance of this websocket thread is created for each strategy instance.
|
||||
"""""
|
||||
class LiveOrderUpdatesStreamer(Thread):
|
||||
def __init__(self, key: Keys, name: str) -> None:
|
||||
self.key = key
|
||||
self.strategy = None
|
||||
self.client = TradingStream(api_key=key.API_KEY, secret_key=key.SECRET_KEY, paper=key.PAPER)
|
||||
Thread.__init__(self, name=name)
|
||||
|
||||
#notif dispatcher - pouze 1 strategie
|
||||
async def distributor(self,data):
|
||||
if self.strategy.symbol == data.order.symbol: await self.strategy.order_updates(data)
|
||||
|
||||
# connects callback to interface object - responses for given symbol are routed to interface callback
|
||||
def connect_callback(self, st):
|
||||
self.strategy = st
|
||||
|
||||
def disconnect_callback(self, st):
|
||||
print("*"*10, "WS Order Update Streamer stopping for", self.strategy.name, "*"*10)
|
||||
self.strategy = None
|
||||
self.client.stop()
|
||||
|
||||
def run(self):
|
||||
## spusti webservice
|
||||
if self.strategy is None:
|
||||
print("connect strategy first")
|
||||
return
|
||||
self.client.subscribe_trade_updates(self.distributor)
|
||||
print("*"*10, "WS Order Update Streamer started for", self.strategy.name, "*"*10)
|
||||
self.client.run()
|
||||
|
||||
207
v2realbot/loader/trade_offline_streamer.py
Normal file
207
v2realbot/loader/trade_offline_streamer.py
Normal file
@ -0,0 +1,207 @@
|
||||
from v2realbot.loader.aggregator import TradeAggregator, TradeAggregator2List, TradeAggregator2Queue
|
||||
from alpaca.trading.requests import GetCalendarRequest
|
||||
from alpaca.trading.client import TradingClient
|
||||
from alpaca.data.live import StockDataStream
|
||||
from v2realbot.config import ACCOUNT1_PAPER_API_KEY, ACCOUNT1_PAPER_SECRET_KEY, DATA_DIR
|
||||
from alpaca.data.enums import DataFeed
|
||||
from alpaca.data.historical import StockHistoricalDataClient
|
||||
from alpaca.data.requests import StockLatestQuoteRequest, StockBarsRequest, StockTradesRequest
|
||||
from threading import Thread, current_thread
|
||||
from v2realbot.utils.utils import parse_alpaca_timestamp, ltp, zoneNY, print
|
||||
from v2realbot.utils.tlog import tlog
|
||||
from datetime import datetime, timedelta
|
||||
from threading import Thread
|
||||
import asyncio
|
||||
from msgpack.ext import Timestamp
|
||||
from msgpack import packb
|
||||
from pandas import to_datetime
|
||||
import pickle
|
||||
import os
|
||||
|
||||
"""
|
||||
Trade offline data streamer, based on Alpaca historical data.
|
||||
"""
|
||||
class Trade_Offline_Streamer(Thread):
|
||||
#pro BT se pripojujeme vzdy k primarnimu uctu - pouze tahame historicka data + calendar
|
||||
client = StockHistoricalDataClient(ACCOUNT1_PAPER_API_KEY, ACCOUNT1_PAPER_SECRET_KEY, raw_data=True)
|
||||
clientTrading = TradingClient(ACCOUNT1_PAPER_API_KEY, ACCOUNT1_PAPER_SECRET_KEY, raw_data=False)
|
||||
def __init__(self, time_from: datetime, time_to: datetime, btdata) -> None:
|
||||
# Call the Thread class's init function
|
||||
Thread.__init__(self)
|
||||
self.uniquesymbols = set()
|
||||
self.streams = []
|
||||
self.to_run = dict()
|
||||
self.time_from = time_from
|
||||
self.time_to = time_to
|
||||
self.btdata = btdata
|
||||
|
||||
def add_stream(self, obj: TradeAggregator):
|
||||
self.streams.append(obj)
|
||||
|
||||
def remove_stream(self, obj):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
#create new asyncio loop in the thread
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
loop.create_task(self.main())
|
||||
loop.run_forever()
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
# Override the run() function of Thread class
|
||||
async def main(self):
|
||||
print(10*"*","Trade OFFLINE streamer STARTED", current_thread().name,10*"*")
|
||||
|
||||
if not self.streams:
|
||||
print("call add streams to queue first")
|
||||
return 0
|
||||
|
||||
#iterujeme nad streamy
|
||||
for i in self.streams:
|
||||
self.uniquesymbols.add(i.symbol)
|
||||
|
||||
ic(self.uniquesymbols)
|
||||
##z unikatnich symbolu naplnime keys pro dictionary
|
||||
# for i in self.uniquesymbols:
|
||||
# self.to_run
|
||||
|
||||
#vytvorime prazdne dict oklicovane unik.symboly a obsahujici prazdne pole
|
||||
self.to_run = {key: [] for key in self.uniquesymbols}
|
||||
#stejne tak pro glob.tridu last price
|
||||
ltp.price = {key: 0 for key in self.uniquesymbols}
|
||||
#pro kazdy symbol do toho pole ulozime instance na spusteni
|
||||
print(self.to_run)
|
||||
for i in self.streams:
|
||||
self.to_run[i.symbol].append(i)
|
||||
|
||||
ic(self.to_run)
|
||||
#prepare data
|
||||
symbpole = []
|
||||
for key in self.uniquesymbols:
|
||||
symbpole.append(key)
|
||||
#print(symbpole))
|
||||
ic(self.time_from.astimezone(tz=zoneNY))
|
||||
ic(self.time_to.astimezone(tz=zoneNY))
|
||||
|
||||
##PREPSAT jednoduse tak, aby podporovalo jen jeden symbol
|
||||
#agregator2list bude mit vstup list
|
||||
|
||||
#REFACTOR STARTS HERE
|
||||
calendar_request = GetCalendarRequest(start=self.time_from,end=self.time_to)
|
||||
cal_dates = self.clientTrading.get_calendar(calendar_request)
|
||||
ic(cal_dates)
|
||||
#zatim podpora pouze main session
|
||||
|
||||
#zatim podpora pouze 1 symbolu, predelat na froloop vsech symbolu ze symbpole
|
||||
#minimalni jednotka pro CACHE je 1 den - a to jen marketopen to marketclose (extended hours not supported yet)
|
||||
for day in cal_dates:
|
||||
print("Processing DAY", day.date)
|
||||
print(day.date)
|
||||
print(day.open)
|
||||
print(day.close)
|
||||
#make it offset aware
|
||||
day.open = day.open.replace(tzinfo=zoneNY)
|
||||
day.close = day.close.replace(tzinfo=zoneNY)
|
||||
|
||||
##pokud datum do je mensi day.open, tak tento den neresime
|
||||
if self.time_to < day.open:
|
||||
print("time_to je pred zacatkem marketu. Vynechavame tento den.")
|
||||
continue
|
||||
|
||||
daily_file = str(symbpole[0]) + '-' + str(int(day.open.timestamp())) + '-' + str(int(day.close.timestamp())) + '.cache'
|
||||
print(daily_file)
|
||||
file_path = DATA_DIR + "/"+daily_file
|
||||
|
||||
if os.path.exists(file_path):
|
||||
##denní file existuje
|
||||
#loadujeme ze souboru
|
||||
#pokud je start_time < trade < end_time
|
||||
#odesíláme do queue
|
||||
#jinak pass
|
||||
with open (file_path, 'rb') as fp:
|
||||
tradesResponse = pickle.load(fp)
|
||||
print("Loading DATA from CACHE", file_path)
|
||||
#daily file doesnt exist
|
||||
else:
|
||||
# TODO refactor pro zpracovani vice symbolu najednou(multithreads), nyni predpokladame pouze 1
|
||||
stockTradeRequest = StockTradesRequest(symbol_or_symbols=symbpole[0], start=day.open,end=day.close)
|
||||
tradesResponse = self.client.get_stock_trades(stockTradeRequest)
|
||||
print("Remote Fetch DAY DATA Complete", day.open, day.close)
|
||||
|
||||
#pokud jde o dnešní den a nebyl konec trhu tak cache neukládáme
|
||||
if day.open < datetime.now().astimezone(zoneNY) < day.close:
|
||||
print("not saving the cache, market still open today")
|
||||
ic(datetime.now().astimezone(zoneNY))
|
||||
ic(day.open, day.close)
|
||||
else:
|
||||
with open(file_path, 'wb') as fp:
|
||||
pickle.dump(tradesResponse, fp)
|
||||
|
||||
#zde už máme daily data
|
||||
#pokud je start_time < trade < end_time
|
||||
#odesíláme do queue
|
||||
#jinak ne
|
||||
|
||||
#TODO pokud data zahrnuji open (tzn. bud cely den(jednotest nebo v ramci vice dni) a nebo jednotest se zacatkem v 9:30 nebo driv.
|
||||
|
||||
#- pockame na trade Q a od nej budeme pocitat
|
||||
# abychom meli zarovnano s tradingview
|
||||
#- zaroven pak cekame na M(market close) a od nej uz take nic dál nepoustime (NOT IMPLEMENTED YET)
|
||||
|
||||
#protze mi chodi data jen v main sessione, pak jediné, kdy nečekáme na Q, je když time_from je větší než day.open
|
||||
# (např. požadovaná start až od 10:00)
|
||||
if self.time_from > day.open:
|
||||
wait_for_q = False
|
||||
else:
|
||||
wait_for_q = True
|
||||
ic(wait_for_q)
|
||||
|
||||
# v tradesResponse je dict = Trades identifikovane symbolem
|
||||
for symbol in tradesResponse:
|
||||
#print(tradesResponse[symbol])
|
||||
celkem = len(tradesResponse[symbol])
|
||||
ic(symbol, celkem)
|
||||
#print("POCET: ", celkem)
|
||||
cnt = 1
|
||||
|
||||
|
||||
for t in tradesResponse[symbol]:
|
||||
|
||||
#protoze je zde cely den, poustime dal, jen ty relevantni
|
||||
#pokud je start_time < trade < end_time
|
||||
#datetime.fromtimestamp(parse_alpaca_timestamp(t['t']))
|
||||
#ic(t['t'])
|
||||
if self.time_from < to_datetime(t['t']) < self.time_to:
|
||||
#poustime dal, jinak ne
|
||||
if wait_for_q:
|
||||
if 'Q' not in t['c']: continue
|
||||
else:
|
||||
ic("Q found poustime dal")
|
||||
wait_for_q = False
|
||||
|
||||
#homogenizace timestampu s online streamem
|
||||
t['t'] = Timestamp.from_unix(to_datetime(t['t']).timestamp())
|
||||
|
||||
#print("PROGRESS ",cnt,"/",celkem)
|
||||
#print(t)
|
||||
#na rozdil od wwebsocketu zde nemame v zaznamu symbol ['S']
|
||||
#vsem streamum na tomto symbolu posilame data - tbd mozna udelat i per stream vlakno
|
||||
for s in self.to_run[symbol]:
|
||||
#print("zaznam",t)
|
||||
#print("Ingest", s, "zaznam", t)
|
||||
await s.ingest_trade(packb(t))
|
||||
cnt += 1
|
||||
#vsem streamum posleme last TODO: (tuto celou cast prepsat a zjednodusit)
|
||||
#po loadovani vsech dnu
|
||||
for s in self.to_run[symbpole[0]]:
|
||||
await s.ingest_trade(packb("last"))
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
print("stoping loop")
|
||||
loop.stop()
|
||||
print(10*"*","Trade OFFLINE streamer STOPPED", current_thread().name,10*"*")
|
||||
|
||||
|
||||
136
v2realbot/loader/trade_ws_streamer.py
Normal file
136
v2realbot/loader/trade_ws_streamer.py
Normal file
@ -0,0 +1,136 @@
|
||||
"""
|
||||
Classes for streamers (websocket and offline)
|
||||
currently only streams are Trades
|
||||
"""
|
||||
from v2realbot.loader.aggregator import TradeAggregator2Queue
|
||||
from alpaca.data.live import StockDataStream
|
||||
from v2realbot.config import ACCOUNT1_PAPER_API_KEY, ACCOUNT1_PAPER_SECRET_KEY, ACCOUNT1_PAPER_FEED
|
||||
from alpaca.data.historical import StockHistoricalDataClient
|
||||
from alpaca.data.requests import StockLatestQuoteRequest, StockBarsRequest, StockTradesRequest
|
||||
from threading import Thread, current_thread
|
||||
from v2realbot.utils.utils import parse_alpaca_timestamp, ltp
|
||||
from datetime import datetime, timedelta
|
||||
from threading import Thread, Lock
|
||||
from msgpack import packb
|
||||
|
||||
"""
|
||||
Shared streamer (can be shared amongst concurrently running strategies)
|
||||
Connects to alpaca websocket client and subscribe for trades on symbols requested
|
||||
by strategies
|
||||
"""
|
||||
class Trade_WS_Streamer(Thread):
|
||||
|
||||
##tento ws streamer je pouze jeden pro vsechny, tzn. vyuziváme natvrdo placena data primarniho uctu (nezalezi jestli paper nebo live)
|
||||
client = StockDataStream(ACCOUNT1_PAPER_API_KEY, ACCOUNT1_PAPER_SECRET_KEY, raw_data=True, websocket_params={}, feed=ACCOUNT1_PAPER_FEED)
|
||||
#uniquesymbols = set()
|
||||
_streams = []
|
||||
#to_run = dict()
|
||||
#lock = Lock()
|
||||
|
||||
def __init__(self, name: str) -> None:
|
||||
# Call the Thread class's init function
|
||||
Thread.__init__(self, name=name)
|
||||
|
||||
def symbol_exists(self, symbol):
|
||||
for i in Trade_WS_Streamer._streams:
|
||||
if i.symbol == symbol:
|
||||
return True
|
||||
return False
|
||||
|
||||
def add_stream(self, obj: TradeAggregator2Queue):
|
||||
print("stav pred pridavanim", Trade_WS_Streamer._streams)
|
||||
Trade_WS_Streamer._streams.append(obj)
|
||||
if Trade_WS_Streamer.client._running is False:
|
||||
print("websocket zatim nebezi, pouze pridavame do pole")
|
||||
else:
|
||||
print("websocket client bezi")
|
||||
if self.symbol_exists(obj.symbol):
|
||||
print("Symbol",obj.symbol,"již je subscribnuty")
|
||||
return
|
||||
Trade_WS_Streamer.client.subscribe_trades(self.datahandler, obj.symbol)
|
||||
|
||||
def remove_stream(self, obj: TradeAggregator2Queue):
|
||||
#delete added stream
|
||||
try:
|
||||
Trade_WS_Streamer._streams.remove(obj)
|
||||
except ValueError:
|
||||
print("value not found in _streams")
|
||||
return
|
||||
#if it is the last item at all, stop the client from running
|
||||
if len(Trade_WS_Streamer._streams) == 0:
|
||||
print("removed last item from WS, stopping the client")
|
||||
Trade_WS_Streamer.client.stop()
|
||||
return
|
||||
|
||||
if not self.symbol_exists(obj.symbol):
|
||||
Trade_WS_Streamer.client.unsubscribe_trades(obj.symbol)
|
||||
print("symbol no longer used, unsubscribed from ", obj.symbol)
|
||||
|
||||
|
||||
# dispatch for all streams
|
||||
@classmethod
|
||||
async def datahandler(cls, data):
|
||||
|
||||
#REFACTOR nemuze byt takto? vyzkouset, pripadne se zbavit to_run dict
|
||||
#overit i kvuli performance
|
||||
for i in cls._streams:
|
||||
if i.symbol == data['S']:
|
||||
await i.ingest_trade(packb(data))
|
||||
|
||||
#pro každý symbol volat příslušné agregátory pro symbol
|
||||
# for i in self.to_run[data['S']]:
|
||||
# #print("ingest pro", data['S'], "volano", i)
|
||||
# await i.ingest_trade(packb(data))
|
||||
# #print("*"*40)
|
||||
|
||||
#zatim vracime do jedne queue - dodelat dynamicky
|
||||
|
||||
# Override the run() function of Thread class
|
||||
def run(self):
|
||||
if len(Trade_WS_Streamer._streams)==0:
|
||||
print("call add streams to queue")
|
||||
print("*"*10, "WS Streamer - run", current_thread().name,"*"*10)
|
||||
|
||||
#iterujeme nad streamy
|
||||
unique = set()
|
||||
for i in self._streams:
|
||||
print("symbol ve streams", i.symbol)
|
||||
unique.add(i.symbol)
|
||||
|
||||
##z unikatnich symbolu naplnime keys pro dictionary
|
||||
#print(self.uniquesymbols)
|
||||
# for i in self.uniquesymbols:
|
||||
# self.to_run
|
||||
|
||||
#TODO nejspis s lockem? kdyz menime pri bezici strategii
|
||||
|
||||
#vytvorime prazdne dict oklicovane unik.symboly a obsahujici prazdne pole
|
||||
#with self.lock:
|
||||
##self.to_run = {key: [] for key in self.uniquesymbols}
|
||||
#stejne tak pro glob.tridu last price
|
||||
#TODO predelat pro concurrency
|
||||
#ltp.price = {key: 0 for key in self.uniquesymbols}
|
||||
#pro kazdy symbol do toho pole ulozime instance na spusteni
|
||||
# print(self.to_run)
|
||||
# for i in self._streams:
|
||||
# self.to_run[i.symbol].append(i)
|
||||
|
||||
# print ("promenna to_run:",self.to_run)
|
||||
|
||||
# sub for unique symbols
|
||||
for i in unique:
|
||||
Trade_WS_Streamer.client.subscribe_trades(Trade_WS_Streamer.datahandler, i)
|
||||
print("subscribed to",i)
|
||||
|
||||
#timto se spusti jenom poprve v 1 vlaknu
|
||||
#ostatni pouze vyuzivaji
|
||||
if Trade_WS_Streamer.client._running is False:
|
||||
print(self.name, "it is not running, starting by calling RUN")
|
||||
print("*"*10, "WS Streamer STARTED", "*"*10)
|
||||
Trade_WS_Streamer.client.run()
|
||||
print("*"*10, "WS Streamer STOPPED", "*"*10)
|
||||
#tímto se spustí pouze 1.vlakno, nicmene subscribe i pripadny unsubscribe zafunguji
|
||||
else:
|
||||
print("Websocket client is running, not calling RUN this time")
|
||||
|
||||
|
||||
270
v2realbot/main.py
Normal file
270
v2realbot/main.py
Normal file
@ -0,0 +1,270 @@
|
||||
import os,sys
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from v2realbot.enums.enums import Mode, Account
|
||||
from v2realbot.config import WEB_API_KEY
|
||||
from datetime import datetime
|
||||
from icecream import install, ic
|
||||
import os
|
||||
from rich import print
|
||||
from threading import current_thread
|
||||
from fastapi import FastAPI, Depends, HTTPException, status
|
||||
from fastapi.security import APIKeyHeader
|
||||
import uvicorn
|
||||
from uuid import UUID
|
||||
import controller.services as cs
|
||||
from v2realbot.common.model import StrategyInstance, RunnerView, RunRequest
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends, HTTPException, status, WebSocketException, Cookie, Query
|
||||
from fastapi.responses import HTMLResponse, FileResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
||||
from typing import Annotated
|
||||
import os
|
||||
import uvicorn
|
||||
import json
|
||||
from queue import Queue, Empty
|
||||
from threading import Thread
|
||||
import asyncio
|
||||
#from async io import Queue, QueueEmpty
|
||||
|
||||
install()
|
||||
ic.configureOutput(includeContext=True)
|
||||
def threadName():
|
||||
return '%s |> ' % str(current_thread().name)
|
||||
ic.configureOutput(prefix=threadName)
|
||||
#ic.disable()
|
||||
"""""
|
||||
Main entry point of the bot. Starts strategies according to config file, each
|
||||
in separate thread.
|
||||
|
||||
CONF:
|
||||
{'general': {'make_network_connection': True, 'ping_time': 1200},
|
||||
'strategies': [{'name': 'Dokupovaci 1', 'symbol': 'BAC'},
|
||||
{'name': 'Vykladaci', 'symbol': 'year'}]}
|
||||
"""""
|
||||
|
||||
# <link href="https://unpkg.com/tabulator-tables/dist/css/tabulator.min.css" rel="stylesheet">
|
||||
# <script type="text/javascript" src="https://unpkg.com/tabulator-tables/dist/js/tabulator.min.js"></script>
|
||||
|
||||
|
||||
X_API_KEY = APIKeyHeader(name='X-API-Key')
|
||||
|
||||
def api_key_auth(api_key: str = Depends(X_API_KEY)):
|
||||
if api_key != WEB_API_KEY:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Forbidden"
|
||||
)
|
||||
|
||||
app = FastAPI()
|
||||
root = os.path.dirname(os.path.abspath(__file__))
|
||||
app.mount("/static", StaticFiles(html=True, directory=os.path.join(root, 'static')), name="static")
|
||||
#app.mount("/", StaticFiles(html=True, directory=os.path.join(root, 'static')), name="www")
|
||||
|
||||
security = HTTPBasic()
|
||||
|
||||
def get_current_username(
|
||||
credentials: Annotated[HTTPBasicCredentials, Depends(security)]
|
||||
):
|
||||
if not (credentials.username == "david") or not (credentials.password == "david"):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect email or password",
|
||||
headers={"WWW-Authenticate": "Basic"},
|
||||
)
|
||||
|
||||
async def get_api_key(
|
||||
websocket: WebSocket,
|
||||
session: Annotated[str | None, Cookie()] = None,
|
||||
api_key: Annotated[str | None, Query()] = None,
|
||||
):
|
||||
if api_key != WEB_API_KEY:
|
||||
raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION)
|
||||
return session or api_key
|
||||
|
||||
#TODO predelat z Async?
|
||||
@app.get("/static")
|
||||
async def get(username: Annotated[str, Depends(get_current_username)]):
|
||||
return FileResponse("index.html")
|
||||
|
||||
@app.websocket("/runners/{runner_id}/ws")
|
||||
async def websocket_endpoint(
|
||||
*,
|
||||
websocket: WebSocket,
|
||||
runner_id: str,
|
||||
api_key: Annotated[str, Depends(get_api_key)],
|
||||
):
|
||||
await websocket.accept()
|
||||
if not cs.is_stratin_running(runner_id):
|
||||
#await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA, reason="Strat not running")
|
||||
raise WebSocketException(code=status.WS_1003_UNSUPPORTED_DATA, reason="Stratin not running.")
|
||||
return
|
||||
else:
|
||||
print("stratin exists")
|
||||
q: Queue = Queue()
|
||||
await cs.stratin_realtime_on(id=runner_id, rtqueue=q)
|
||||
|
||||
# tx task; reads data from queue and sends to websocket
|
||||
async def websocket_tx_task(ws, _q):
|
||||
print("Starting WS tx...")
|
||||
|
||||
while True:
|
||||
try:
|
||||
data = _q.get(timeout=10)
|
||||
if data=="break":
|
||||
break
|
||||
await ws.send_text(data)
|
||||
print("WSTX thread received data", data)
|
||||
except Empty:
|
||||
print("WSTX thread Heartbeat. No data received from queue.")
|
||||
continue
|
||||
except WebSocketDisconnect:
|
||||
print("WSTX thread disconnected - terminating tx job")
|
||||
break
|
||||
|
||||
print("WSTX thread terminated")
|
||||
|
||||
def websocket_tx_task_wrapper(ws, _q):
|
||||
asyncio.run(websocket_tx_task(ws, _q))
|
||||
|
||||
ws_tx_thread = Thread(target=websocket_tx_task_wrapper, args = (websocket, q,))
|
||||
ws_tx_thread.start()
|
||||
try:
|
||||
while True:
|
||||
data = await websocket.receive_text()
|
||||
print(f"WS RX: {data}")
|
||||
# data = q.get()
|
||||
# print("WSGOTDATA",data)
|
||||
#await websocket.receive_text()
|
||||
# await websocket.send_text(
|
||||
# f"Session cookie or query token value is: {cookie_or_token}"
|
||||
# )
|
||||
# data = {'high': 195,
|
||||
# 'low': 180,
|
||||
# 'volume': 123,
|
||||
# 'close': 185,
|
||||
# 'hlcc4': 123,
|
||||
# 'open': 190,
|
||||
# 'time': "2019-05-25",
|
||||
# 'trades':123,
|
||||
# 'resolution':123,
|
||||
# 'confirmed': 123,
|
||||
# 'vwap': 123,
|
||||
# 'updated': 123,
|
||||
# 'index': 123}
|
||||
#print("WSRT received data", data)
|
||||
#await websocket.send_text(data)
|
||||
except WebSocketDisconnect:
|
||||
print("CLIENT DISCONNECTED for", runner_id)
|
||||
finally:
|
||||
q.put("break")
|
||||
await cs.stratin_realtime_off(runner_id)
|
||||
|
||||
@app.get("/threads/", dependencies=[Depends(api_key_auth)])
|
||||
def _get_all_threads():
|
||||
res, set =cs.get_all_threads()
|
||||
if res == 0:
|
||||
return set
|
||||
else:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"No data found")
|
||||
|
||||
@app.get("/runners/", dependencies=[Depends(api_key_auth)])
|
||||
def _get_all_runners() -> list[RunnerView]:
|
||||
res, set =cs.get_all_runners()
|
||||
if res == 0:
|
||||
return set
|
||||
else:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"No data found")
|
||||
|
||||
@app.get("/runners/{runner_id}", dependencies=[Depends(api_key_auth)])
|
||||
def _get_runner(runner_id) -> RunnerView:
|
||||
res, set = cs.get_runner(runner_id)
|
||||
if res == 0:
|
||||
return set
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail=f"No runner with id: {runner_id} a {set}")
|
||||
|
||||
|
||||
@app.get("/stratins/", dependencies=[Depends(api_key_auth)])
|
||||
def _get_all_stratins() -> list[StrategyInstance]:
|
||||
res, set =cs.get_all_stratins()
|
||||
if res == 0:
|
||||
return set
|
||||
else:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"No data found")
|
||||
|
||||
@app.post("/stratins/", dependencies=[Depends(api_key_auth)], status_code=status.HTTP_200_OK)
|
||||
def _create_stratin(new_stratin: StrategyInstance):
|
||||
res, id = cs.create_stratin(si=new_stratin)
|
||||
if res == 0: return id
|
||||
else:
|
||||
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error not created: {res}:{id}")
|
||||
|
||||
@app.patch("/stratins/{stratin_id}", dependencies=[Depends(api_key_auth)])
|
||||
def _modify_stratin(stratin: StrategyInstance, stratin_id: UUID):
|
||||
if cs.is_stratin_running(id=stratin_id):
|
||||
res,id = cs.modify_stratin_running(si=stratin, id=stratin_id)
|
||||
else:
|
||||
res, id = cs.modify_stratin(si=stratin, id=stratin_id)
|
||||
if res == 0: return id
|
||||
elif res == -2:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Error not found: {res}:{id}")
|
||||
else:
|
||||
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error not changed: {res}:{id}")
|
||||
|
||||
@app.get("/stratins/{stratin_id}", dependencies=[Depends(api_key_auth)])
|
||||
def _get_stratin(stratin_id) -> StrategyInstance:
|
||||
res, set = cs.get_stratin(stratin_id)
|
||||
if res == 0:
|
||||
return set
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail=f"No stratin with id: {stratin_id} a {set}")
|
||||
|
||||
@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):
|
||||
res, id = cs.run_stratin(id=stratin_id, runReq=runReq)
|
||||
if res == 0: return id
|
||||
elif res < 0:
|
||||
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error: {res}:{id}")
|
||||
|
||||
@app.put("/stratins/{stratin_id}/pause", dependencies=[Depends(api_key_auth)], status_code=status.HTTP_200_OK)
|
||||
def _pause_stratin(stratin_id):
|
||||
res, id = cs.pause_stratin(id=stratin_id)
|
||||
if res == 0: return id
|
||||
elif res < 0:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Error: {res}:{id}")
|
||||
|
||||
@app.put("/stratins/{stratin_id}/stop", dependencies=[Depends(api_key_auth)], status_code=status.HTTP_200_OK)
|
||||
def _stop_stratin(stratin_id):
|
||||
res, id = cs.stop_stratin(id=stratin_id)
|
||||
if res == 0: return id
|
||||
elif res < 0:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Error: {res}:{id}")
|
||||
|
||||
@app.delete("/stratins/{stratin_id}", dependencies=[Depends(api_key_auth)], status_code=status.HTTP_200_OK)
|
||||
def _delete_stratin(stratin_id):
|
||||
res, id = cs.delete_stratin(id=stratin_id)
|
||||
if res == 0: return id
|
||||
elif res < 0:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Error: {res}:{id}")
|
||||
|
||||
@app.put("/stratins/stop", dependencies=[Depends(api_key_auth)], status_code=status.HTTP_200_OK)
|
||||
def stop_all_stratins():
|
||||
res, id = cs.stop_stratin()
|
||||
if res == 0: return id
|
||||
elif res < 0:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Error: {res}:{id}")
|
||||
|
||||
#join cekej na dokonceni vsech
|
||||
for i in cs.db.runners:
|
||||
i.run_thread.join()
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run("__main__:app", host="0.0.0.0", port=8000, reload=False)
|
||||
|
||||
##TODO pridat moznost behu na PAPER a LIVE per strategie
|
||||
|
||||
# zjistit zda order notification websocket muze bezet na obou soucasne
|
||||
# pokud ne, mohl bych vyuzivat jen zive data
|
||||
# a pro paper trading(live interface) a notifications bych pouzival separatni paper ucet
|
||||
# to by asi slo
|
||||
|
||||
252
v2realbot/static/index.html
Normal file
252
v2realbot/static/index.html
Normal file
@ -0,0 +1,252 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Chat</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<div class="mainConteiner">
|
||||
<div class="realtime">
|
||||
<h1>Realtime chart</h1>
|
||||
<h2>Your ID: <span id="ws-id"></span></h2>
|
||||
<h3>Status: <span id="status">Not connected</span></h3>
|
||||
<form action="" onsubmit="sendMessage(event)">
|
||||
<label>Runner ID: <input type="text" id="runnerId" autocomplete="off" value="foo"/></label>
|
||||
<button onclick="connect(event)" id="bt-conn">Connect</button>
|
||||
<button onclick="disconnect(event)" id="bt-disc" style="display: None">Disconnect</button>
|
||||
<hr>
|
||||
<label>Message: <input type="text" id="messageText" autocomplete="off"/></label>
|
||||
<button>Send</button>
|
||||
</form>
|
||||
<ul id='messages'>
|
||||
</ul>
|
||||
<div id="chart"></div>
|
||||
<div id="conteiner"></div>
|
||||
</div>
|
||||
|
||||
<div id="controls">
|
||||
<label>API-KEY: <input type="password" id="api-key" autocomplete="off"/></label>
|
||||
<button onclick="store_api_key(event)" id="bt-store">Store</button>
|
||||
</div>
|
||||
<div id="runner-table">
|
||||
<button id="button_pause" class="btn btn-success">Pause/Unpause</button>
|
||||
<button id="button_stop" class="btn btn-success">Stop</button>
|
||||
<button id="button_stopall" class="btn btn-success">Stop All</button>
|
||||
<button id="button_refresh" class="btn btn-success">Refresh</button>
|
||||
|
||||
<table id="runnerTable" class="display" style="width:100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Started</th>
|
||||
<th>Mode</th>
|
||||
<th>Account</th>
|
||||
<th>Paused</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="runnerModal" class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<form method="post" id="stopForm">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title"><i class="fa fa-plus"></i> Stop Strategy</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group"
|
||||
<label for="runnerid" class="control-label">Id</label>
|
||||
<input type="text" class="form-control" id="runnerid" name="runnerid" placeholder="id">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input type="submit" name="stop" id="stop" class="btn btn-info" value="stop" />
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div id="stratin-table">
|
||||
<button id="button_add" class="btn btn-success">Add</button><button id="button_edit" class="btn btn-success">Edit</button><button id="button_delete" class="btn btn-success">Delete</button><button id="button_run" class="btn btn-success">Run Strategy</button>
|
||||
<table id="stratinTable" class="display" style="width:100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Id2</th>
|
||||
<th>Name</th>
|
||||
<th>Symbol</th>
|
||||
<th>class</th>
|
||||
<th>script</th>
|
||||
<th>OR</th>
|
||||
<th>CR</th>
|
||||
<th>Stratvars</th>
|
||||
<th>add_data</th>
|
||||
<th>note</th>
|
||||
<th>history</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="recordModal" class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<form method="post" id="recordForm">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title"><i class="fa fa-plus"></i> Add Record</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group"
|
||||
<label for="id" class="control-label">Id</label>
|
||||
<input type="text" class="form-control" id="id" name="id" placeholder="id" readonly>
|
||||
</div>
|
||||
<div class="form-group"
|
||||
<label for="id2" class="control-label">Id2</label>
|
||||
<input type="text" class="form-control" id="id2" name="id2" placeholder="id2" required>
|
||||
</div>
|
||||
<div class="form-group"
|
||||
<label for="name" class="control-label">Name</label>
|
||||
<input type="text" class="form-control" id="name" name="name" placeholder="Name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="symbol" class="control-label">symbol</label>
|
||||
<input type="text" class="form-control" id="symbol" name="symbol" placeholder="Symbol" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="class_name" class="control-label">class_name</label>
|
||||
<input type="text" class="form-control" id="class_name" name="class_name" placeholder="class_name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="script" class="control-label">script</label>
|
||||
<input type="text" class="form-control" id="script" name="script" placeholder="script" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="open_rush" class="control-label">open_rush</label>
|
||||
<input type="number" class="form-control" id="open_rush" name="open_rush" placeholder="open_rush" value=0 required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="close_rush" class="control-label">close_rush</label>
|
||||
<input type="number" class="form-control" id="close_rush" name="close_rush" placeholder="close_rush" value=0 required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="stratvars_conf" class="control-label">stratvars_conf</label>
|
||||
<textarea class="form-control" rows="8" id="stratvars_conf" name="stratvars_conf" required></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="add_data_conf" class="control-label">add_data_conf</label>
|
||||
<textarea class="form-control" rows="7" id="add_data_conf" name="add_data_conf" required></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="note" class="control-label">note</label>
|
||||
<textarea class="form-control" rows="2" id="note" name="note"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="history" class="control-label">history</label>
|
||||
<textarea class="form-control" rows="3" id="history" name="history"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<!--<input type="hidden" name="id" id="id" />-->
|
||||
<!--<input type="hidden" name="action" id="action" value="" />-->
|
||||
<input type="submit" name="save" id="save" class="btn btn-info" value="Save" />
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="delModal" class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<form method="post" id="delForm">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title"><i class="fa fa-plus"></i> Delete Record</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group"
|
||||
<label for="delid" class="control-label">Id</label>
|
||||
<input type="text" class="form-control" id="delid" name="delid" placeholder="id">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input type="submit" name="delete" id="delete" class="btn btn-info" value="Delete" />
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="runModal" class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<form method="post" id="runForm">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title"><i class="fa fa-plus"></i> Run strategy</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group"
|
||||
<label for="runid" class="control-label">Id</label>
|
||||
<input type="text" class="form-control" id="runid" name="runid" placeholder="id" readonly>
|
||||
</div>
|
||||
<div class="form-group"
|
||||
<label for="mode" class="control-label">Mode</label>
|
||||
<select class="form-control" id="mode" name="mode"><option value="paper">paper</option><option value="live">live</option><option value="backtest">backtest</option></select>
|
||||
</div>
|
||||
<div class="form-group"
|
||||
<label for="account" class="control-label">Account</label>
|
||||
<select class="form-control" id="account" name="account"><option value="ACCOUNT1">ACCOUNT1</option><option value="ACCOUNT2">ACCOUNT2</option></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="debug" class="control-label">debug</label>
|
||||
<select class="form-control" id="debug" name="debug"><option value="true">true</option><option value="false" selected>false</option></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="bt_from" class="control-label">bt_from</label>
|
||||
<input type="text" class="form-control" id="bt_from" name="bt_from" placeholder="2023-04-06T09:00:00Z">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="bt_to" class="control-label">bt_to</label>
|
||||
<input type="text" class="form-control" id="bt_to" name="bt_to" placeholder="2023-04-06T09:00:00Z">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="cash" class="control-label">cash</label>
|
||||
<input type="number" class="form-control" id="cash" name="cash" placeholder="cash" value=0>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<!--<input type="hidden" name="id" id="id" />-->
|
||||
<!--<input type="hidden" name="action" id="action" value="" />-->
|
||||
<input type="submit" name="run" id="run" class="btn btn-info" value="Run" />
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script type="text/javascript" src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"
|
||||
></script>
|
||||
<script src="/static/js/mywebsocket.js"></script>
|
||||
<script src="/static/js/mychart.js"></script>
|
||||
<script src="/static/js/mytables.js"></script>
|
||||
<script src="/static/js/jquery.serializejson.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
1
v2realbot/static/js/dataTables.bootstrap.min.css
vendored
Normal file
1
v2realbot/static/js/dataTables.bootstrap.min.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
table.dataTable{clear:both;margin-top:6px !important;margin-bottom:6px !important;max-width:none !important;border-collapse:separate !important}table.dataTable td,table.dataTable th{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}table.dataTable td.dataTables_empty,table.dataTable th.dataTables_empty{text-align:center}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}div.dataTables_wrapper div.dataTables_length label{font-weight:normal;text-align:left;white-space:nowrap}div.dataTables_wrapper div.dataTables_length select{width:75px;display:inline-block}div.dataTables_wrapper div.dataTables_filter{text-align:right}div.dataTables_wrapper div.dataTables_filter label{font-weight:normal;white-space:nowrap;text-align:left}div.dataTables_wrapper div.dataTables_filter input{margin-left:0.5em;display:inline-block;width:auto}div.dataTables_wrapper div.dataTables_info{padding-top:8px;white-space:nowrap}div.dataTables_wrapper div.dataTables_paginate{margin:0;white-space:nowrap;text-align:right}div.dataTables_wrapper div.dataTables_paginate ul.pagination{margin:2px 0;white-space:nowrap}div.dataTables_wrapper div.dataTables_processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-26px;text-align:center;padding:1em 0}table.dataTable thead>tr>th.sorting_asc,table.dataTable thead>tr>th.sorting_desc,table.dataTable thead>tr>th.sorting,table.dataTable thead>tr>td.sorting_asc,table.dataTable thead>tr>td.sorting_desc,table.dataTable thead>tr>td.sorting{padding-right:30px}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;position:relative}table.dataTable thead .sorting:after,table.dataTable thead .sorting_asc:after,table.dataTable thead .sorting_desc:after,table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{position:absolute;bottom:8px;right:8px;display:block;font-family:'Glyphicons Halflings';opacity:0.5}table.dataTable thead .sorting:after{opacity:0.2;content:"\e150"}table.dataTable thead .sorting_asc:after{content:"\e155"}table.dataTable thead .sorting_desc:after{content:"\e156"}table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{color:#eee}div.dataTables_scrollHead table.dataTable{margin-bottom:0 !important}div.dataTables_scrollBody table{border-top:none;margin-top:0 !important;margin-bottom:0 !important}div.dataTables_scrollBody table thead .sorting:after,div.dataTables_scrollBody table thead .sorting_asc:after,div.dataTables_scrollBody table thead .sorting_desc:after{display:none}div.dataTables_scrollBody table tbody tr:first-child th,div.dataTables_scrollBody table tbody tr:first-child td{border-top:none}div.dataTables_scrollFoot table{margin-top:0 !important;border-top:none}@media screen and (max-width: 767px){div.dataTables_wrapper div.dataTables_length,div.dataTables_wrapper div.dataTables_filter,div.dataTables_wrapper div.dataTables_info,div.dataTables_wrapper div.dataTables_paginate{text-align:center}}table.dataTable.table-condensed>thead>tr>th{padding-right:20px}table.dataTable.table-condensed .sorting:after,table.dataTable.table-condensed .sorting_asc:after,table.dataTable.table-condensed .sorting_desc:after{top:6px;right:6px}table.table-bordered.dataTable th,table.table-bordered.dataTable td{border-left-width:0}table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable td:last-child,table.table-bordered.dataTable td:last-child{border-right-width:0}table.table-bordered.dataTable tbody th,table.table-bordered.dataTable tbody td{border-bottom-width:0}div.dataTables_scrollHead table.table-bordered{border-bottom-width:0}div.table-responsive>div.dataTables_wrapper>div.row{margin:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:first-child{padding-left:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:last-child{padding-right:0}
|
||||
8
v2realbot/static/js/dataTables.bootstrap.min.js
vendored
Normal file
8
v2realbot/static/js/dataTables.bootstrap.min.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*!
|
||||
DataTables Bootstrap 3 integration
|
||||
©2011-2015 SpryMedia Ltd - datatables.net/license
|
||||
*/
|
||||
(function(b){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(a){return b(a,window,document)}):"object"===typeof exports?module.exports=function(a,d){a||(a=window);if(!d||!d.fn.dataTable)d=require("datatables.net")(a,d).$;return b(d,a,a.document)}:b(jQuery,window,document)})(function(b,a,d){var f=b.fn.dataTable;b.extend(!0,f.defaults,{dom:"<'row'<'col-sm-6'l><'col-sm-6'f>><'row'<'col-sm-12'tr>><'row'<'col-sm-5'i><'col-sm-7'p>>",renderer:"bootstrap"});b.extend(f.ext.classes,
|
||||
{sWrapper:"dataTables_wrapper form-inline dt-bootstrap",sFilterInput:"form-control input-sm",sLengthSelect:"form-control input-sm",sProcessing:"dataTables_processing panel panel-default"});f.ext.renderer.pageButton.bootstrap=function(a,h,r,m,j,n){var o=new f.Api(a),s=a.oClasses,k=a.oLanguage.oPaginate,t=a.oLanguage.oAria.paginate||{},e,g,p=0,q=function(d,f){var l,h,i,c,m=function(a){a.preventDefault();!b(a.currentTarget).hasClass("disabled")&&o.page()!=a.data.action&&o.page(a.data.action).draw("page")};
|
||||
l=0;for(h=f.length;l<h;l++)if(c=f[l],b.isArray(c))q(d,c);else{g=e="";switch(c){case "ellipsis":e="…";g="disabled";break;case "first":e=k.sFirst;g=c+(0<j?"":" disabled");break;case "previous":e=k.sPrevious;g=c+(0<j?"":" disabled");break;case "next":e=k.sNext;g=c+(j<n-1?"":" disabled");break;case "last":e=k.sLast;g=c+(j<n-1?"":" disabled");break;default:e=c+1,g=j===c?"active":""}e&&(i=b("<li>",{"class":s.sPageButton+" "+g,id:0===r&&"string"===typeof c?a.sTableId+"_"+c:null}).append(b("<a>",{href:"#",
|
||||
"aria-controls":a.sTableId,"aria-label":t[c],"data-dt-idx":p,tabindex:a.iTabIndex}).html(e)).appendTo(d),a.oApi._fnBindAction(i,{action:c},m),p++)}},i;try{i=b(h).find(d.activeElement).data("dt-idx")}catch(u){}q(b(h).empty().html('<ul class="pagination"/>').children("ul"),m);i&&b(h).find("[data-dt-idx="+i+"]").focus()};return f});
|
||||
166
v2realbot/static/js/jquery.dataTables.min.js
vendored
Normal file
166
v2realbot/static/js/jquery.dataTables.min.js
vendored
Normal file
@ -0,0 +1,166 @@
|
||||
/*!
|
||||
DataTables 1.10.12
|
||||
©2008-2015 SpryMedia Ltd - datatables.net/license
|
||||
*/
|
||||
(function(h){"function"===typeof define&&define.amd?define(["jquery"],function(D){return h(D,window,document)}):"object"===typeof exports?module.exports=function(D,I){D||(D=window);I||(I="undefined"!==typeof window?require("jquery"):require("jquery")(D));return h(I,D,D.document)}:h(jQuery,window,document)})(function(h,D,I,k){function X(a){var b,c,d={};h.each(a,function(e){if((b=e.match(/^([^A-Z]+?)([A-Z])/))&&-1!=="a aa ai ao as b fn i m o s ".indexOf(b[1]+" "))c=e.replace(b[0],b[2].toLowerCase()),
|
||||
d[c]=e,"o"===b[1]&&X(a[e])});a._hungarianMap=d}function K(a,b,c){a._hungarianMap||X(a);var d;h.each(b,function(e){d=a._hungarianMap[e];if(d!==k&&(c||b[d]===k))"o"===d.charAt(0)?(b[d]||(b[d]={}),h.extend(!0,b[d],b[e]),K(a[d],b[d],c)):b[d]=b[e]})}function Da(a){var b=m.defaults.oLanguage,c=a.sZeroRecords;!a.sEmptyTable&&(c&&"No data available in table"===b.sEmptyTable)&&E(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&(c&&"Loading..."===b.sLoadingRecords)&&E(a,a,"sZeroRecords","sLoadingRecords");
|
||||
a.sInfoThousands&&(a.sThousands=a.sInfoThousands);(a=a.sDecimal)&&db(a)}function eb(a){A(a,"ordering","bSort");A(a,"orderMulti","bSortMulti");A(a,"orderClasses","bSortClasses");A(a,"orderCellsTop","bSortCellsTop");A(a,"order","aaSorting");A(a,"orderFixed","aaSortingFixed");A(a,"paging","bPaginate");A(a,"pagingType","sPaginationType");A(a,"pageLength","iDisplayLength");A(a,"searching","bFilter");"boolean"===typeof a.sScrollX&&(a.sScrollX=a.sScrollX?"100%":"");"boolean"===typeof a.scrollX&&(a.scrollX=
|
||||
a.scrollX?"100%":"");if(a=a.aoSearchCols)for(var b=0,c=a.length;b<c;b++)a[b]&&K(m.models.oSearch,a[b])}function fb(a){A(a,"orderable","bSortable");A(a,"orderData","aDataSort");A(a,"orderSequence","asSorting");A(a,"orderDataType","sortDataType");var b=a.aDataSort;b&&!h.isArray(b)&&(a.aDataSort=[b])}function gb(a){if(!m.__browser){var b={};m.__browser=b;var c=h("<div/>").css({position:"fixed",top:0,left:0,height:1,width:1,overflow:"hidden"}).append(h("<div/>").css({position:"absolute",top:1,left:1,
|
||||
width:100,overflow:"scroll"}).append(h("<div/>").css({width:"100%",height:10}))).appendTo("body"),d=c.children(),e=d.children();b.barWidth=d[0].offsetWidth-d[0].clientWidth;b.bScrollOversize=100===e[0].offsetWidth&&100!==d[0].clientWidth;b.bScrollbarLeft=1!==Math.round(e.offset().left);b.bBounding=c[0].getBoundingClientRect().width?!0:!1;c.remove()}h.extend(a.oBrowser,m.__browser);a.oScroll.iBarWidth=m.__browser.barWidth}function hb(a,b,c,d,e,f){var g,j=!1;c!==k&&(g=c,j=!0);for(;d!==e;)a.hasOwnProperty(d)&&
|
||||
(g=j?b(g,a[d],d,a):a[d],j=!0,d+=f);return g}function Ea(a,b){var c=m.defaults.column,d=a.aoColumns.length,c=h.extend({},m.models.oColumn,c,{nTh:b?b:I.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=h.extend({},m.models.oSearch,c[d]);ja(a,d,h(b).data())}function ja(a,b,c){var b=a.aoColumns[b],d=a.oClasses,e=h(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=e.attr("width")||null;var f=
|
||||
(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);f&&(b.sWidthOrig=f[1])}c!==k&&null!==c&&(fb(c),K(m.defaults.column,c),c.mDataProp!==k&&!c.mData&&(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),h.extend(b,c),E(b,c,"sWidth","sWidthOrig"),c.iDataSort!==k&&(b.aDataSort=[c.iDataSort]),E(b,c,"aDataSort"));var g=b.mData,j=Q(g),i=b.mRender?Q(b.mRender):null,c=function(a){return"string"===typeof a&&-1!==a.indexOf("@")};b._bAttrSrc=h.isPlainObject(g)&&
|
||||
(c(g.sort)||c(g.type)||c(g.filter));b._setter=null;b.fnGetData=function(a,b,c){var d=j(a,b,k,c);return i&&b?i(d,b,a,c):d};b.fnSetData=function(a,b,c){return R(g)(a,b,c)};"number"!==typeof g&&(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==h.inArray("asc",b.asSorting);c=-1!==h.inArray("desc",b.asSorting);!b.bSortable||!a&&!c?(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI=""):a&&!c?(b.sSortingClass=d.sSortableAsc,b.sSortingClassJUI=d.sSortJUIAscAllowed):
|
||||
!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI)}function Y(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Fa(a);for(var c=0,d=b.length;c<d;c++)b[c].nTh.style.width=b[c].sWidth}b=a.oScroll;(""!==b.sY||""!==b.sX)&&ka(a);u(a,null,"column-sizing",[a])}function Z(a,b){var c=la(a,"bVisible");return"number"===typeof c[b]?c[b]:null}function $(a,b){var c=la(a,"bVisible"),c=h.inArray(b,c);return-1!==c?c:null}
|
||||
function aa(a){var b=0;h.each(a.aoColumns,function(a,d){d.bVisible&&"none"!==h(d.nTh).css("display")&&b++});return b}function la(a,b){var c=[];h.map(a.aoColumns,function(a,e){a[b]&&c.push(e)});return c}function Ga(a){var b=a.aoColumns,c=a.aoData,d=m.ext.type.detect,e,f,g,j,i,h,l,q,t;e=0;for(f=b.length;e<f;e++)if(l=b[e],t=[],!l.sType&&l._sManualType)l.sType=l._sManualType;else if(!l.sType){g=0;for(j=d.length;g<j;g++){i=0;for(h=c.length;i<h;i++){t[i]===k&&(t[i]=B(a,i,e,"type"));q=d[g](t[i],a);if(!q&&
|
||||
g!==d.length-1)break;if("html"===q)break}if(q){l.sType=q;break}}l.sType||(l.sType="string")}}function ib(a,b,c,d){var e,f,g,j,i,n,l=a.aoColumns;if(b)for(e=b.length-1;0<=e;e--){n=b[e];var q=n.targets!==k?n.targets:n.aTargets;h.isArray(q)||(q=[q]);f=0;for(g=q.length;f<g;f++)if("number"===typeof q[f]&&0<=q[f]){for(;l.length<=q[f];)Ea(a);d(q[f],n)}else if("number"===typeof q[f]&&0>q[f])d(l.length+q[f],n);else if("string"===typeof q[f]){j=0;for(i=l.length;j<i;j++)("_all"==q[f]||h(l[j].nTh).hasClass(q[f]))&&
|
||||
d(j,n)}}if(c){e=0;for(a=c.length;e<a;e++)d(e,c[e])}}function N(a,b,c,d){var e=a.aoData.length,f=h.extend(!0,{},m.models.oRow,{src:c?"dom":"data",idx:e});f._aData=b;a.aoData.push(f);for(var g=a.aoColumns,j=0,i=g.length;j<i;j++)g[j].sType=null;a.aiDisplayMaster.push(e);b=a.rowIdFn(b);b!==k&&(a.aIds[b]=f);(c||!a.oFeatures.bDeferRender)&&Ha(a,e,c,d);return e}function ma(a,b){var c;b instanceof h||(b=h(b));return b.map(function(b,e){c=Ia(a,e);return N(a,c.data,e,c.cells)})}function B(a,b,c,d){var e=a.iDraw,
|
||||
f=a.aoColumns[c],g=a.aoData[b]._aData,j=f.sDefaultContent,i=f.fnGetData(g,d,{settings:a,row:b,col:c});if(i===k)return a.iDrawError!=e&&null===j&&(L(a,0,"Requested unknown parameter "+("function"==typeof f.mData?"{function}":"'"+f.mData+"'")+" for row "+b+", column "+c,4),a.iDrawError=e),j;if((i===g||null===i)&&null!==j&&d!==k)i=j;else if("function"===typeof i)return i.call(g);return null===i&&"display"==d?"":i}function jb(a,b,c,d){a.aoColumns[c].fnSetData(a.aoData[b]._aData,d,{settings:a,row:b,col:c})}
|
||||
function Ja(a){return h.map(a.match(/(\\.|[^\.])+/g)||[""],function(a){return a.replace(/\\./g,".")})}function Q(a){if(h.isPlainObject(a)){var b={};h.each(a,function(a,c){c&&(b[a]=Q(c))});return function(a,c,f,g){var j=b[c]||b._;return j!==k?j(a,c,f,g):a}}if(null===a)return function(a){return a};if("function"===typeof a)return function(b,c,f,g){return a(b,c,f,g)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var c=function(a,b,f){var g,j;if(""!==f){j=Ja(f);
|
||||
for(var i=0,n=j.length;i<n;i++){f=j[i].match(ba);g=j[i].match(U);if(f){j[i]=j[i].replace(ba,"");""!==j[i]&&(a=a[j[i]]);g=[];j.splice(0,i+1);j=j.join(".");if(h.isArray(a)){i=0;for(n=a.length;i<n;i++)g.push(c(a[i],b,j))}a=f[0].substring(1,f[0].length-1);a=""===a?g:g.join(a);break}else if(g){j[i]=j[i].replace(U,"");a=a[j[i]]();continue}if(null===a||a[j[i]]===k)return k;a=a[j[i]]}}return a};return function(b,e){return c(b,e,a)}}return function(b){return b[a]}}function R(a){if(h.isPlainObject(a))return R(a._);
|
||||
if(null===a)return function(){};if("function"===typeof a)return function(b,d,e){a(b,"set",d,e)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var b=function(a,d,e){var e=Ja(e),f;f=e[e.length-1];for(var g,j,i=0,n=e.length-1;i<n;i++){g=e[i].match(ba);j=e[i].match(U);if(g){e[i]=e[i].replace(ba,"");a[e[i]]=[];f=e.slice();f.splice(0,i+1);g=f.join(".");if(h.isArray(d)){j=0;for(n=d.length;j<n;j++)f={},b(f,d[j],g),a[e[i]].push(f)}else a[e[i]]=d;return}j&&(e[i]=e[i].replace(U,
|
||||
""),a=a[e[i]](d));if(null===a[e[i]]||a[e[i]]===k)a[e[i]]={};a=a[e[i]]}if(f.match(U))a[f.replace(U,"")](d);else a[f.replace(ba,"")]=d};return function(c,d){return b(c,d,a)}}return function(b,d){b[a]=d}}function Ka(a){return G(a.aoData,"_aData")}function na(a){a.aoData.length=0;a.aiDisplayMaster.length=0;a.aiDisplay.length=0;a.aIds={}}function oa(a,b,c){for(var d=-1,e=0,f=a.length;e<f;e++)a[e]==b?d=e:a[e]>b&&a[e]--; -1!=d&&c===k&&a.splice(d,1)}function ca(a,b,c,d){var e=a.aoData[b],f,g=function(c,d){for(;c.childNodes.length;)c.removeChild(c.firstChild);
|
||||
c.innerHTML=B(a,b,d,"display")};if("dom"===c||(!c||"auto"===c)&&"dom"===e.src)e._aData=Ia(a,e,d,d===k?k:e._aData).data;else{var j=e.anCells;if(j)if(d!==k)g(j[d],d);else{c=0;for(f=j.length;c<f;c++)g(j[c],c)}}e._aSortData=null;e._aFilterData=null;g=a.aoColumns;if(d!==k)g[d].sType=null;else{c=0;for(f=g.length;c<f;c++)g[c].sType=null;La(a,e)}}function Ia(a,b,c,d){var e=[],f=b.firstChild,g,j,i=0,n,l=a.aoColumns,q=a._rowReadObject,d=d!==k?d:q?{}:[],t=function(a,b){if("string"===typeof a){var c=a.indexOf("@");
|
||||
-1!==c&&(c=a.substring(c+1),R(a)(d,b.getAttribute(c)))}},S=function(a){if(c===k||c===i)j=l[i],n=h.trim(a.innerHTML),j&&j._bAttrSrc?(R(j.mData._)(d,n),t(j.mData.sort,a),t(j.mData.type,a),t(j.mData.filter,a)):q?(j._setter||(j._setter=R(j.mData)),j._setter(d,n)):d[i]=n;i++};if(f)for(;f;){g=f.nodeName.toUpperCase();if("TD"==g||"TH"==g)S(f),e.push(f);f=f.nextSibling}else{e=b.anCells;f=0;for(g=e.length;f<g;f++)S(e[f])}if(b=b.firstChild?b:b.nTr)(b=b.getAttribute("id"))&&R(a.rowId)(d,b);return{data:d,cells:e}}
|
||||
function Ha(a,b,c,d){var e=a.aoData[b],f=e._aData,g=[],j,i,n,l,q;if(null===e.nTr){j=c||I.createElement("tr");e.nTr=j;e.anCells=g;j._DT_RowIndex=b;La(a,e);l=0;for(q=a.aoColumns.length;l<q;l++){n=a.aoColumns[l];i=c?d[l]:I.createElement(n.sCellType);i._DT_CellIndex={row:b,column:l};g.push(i);if((!c||n.mRender||n.mData!==l)&&(!h.isPlainObject(n.mData)||n.mData._!==l+".display"))i.innerHTML=B(a,b,l,"display");n.sClass&&(i.className+=" "+n.sClass);n.bVisible&&!c?j.appendChild(i):!n.bVisible&&c&&i.parentNode.removeChild(i);
|
||||
n.fnCreatedCell&&n.fnCreatedCell.call(a.oInstance,i,B(a,b,l),f,b,l)}u(a,"aoRowCreatedCallback",null,[j,f,b])}e.nTr.setAttribute("role","row")}function La(a,b){var c=b.nTr,d=b._aData;if(c){var e=a.rowIdFn(d);e&&(c.id=e);d.DT_RowClass&&(e=d.DT_RowClass.split(" "),b.__rowc=b.__rowc?pa(b.__rowc.concat(e)):e,h(c).removeClass(b.__rowc.join(" ")).addClass(d.DT_RowClass));d.DT_RowAttr&&h(c).attr(d.DT_RowAttr);d.DT_RowData&&h(c).data(d.DT_RowData)}}function kb(a){var b,c,d,e,f,g=a.nTHead,j=a.nTFoot,i=0===
|
||||
h("th, td",g).length,n=a.oClasses,l=a.aoColumns;i&&(e=h("<tr/>").appendTo(g));b=0;for(c=l.length;b<c;b++)f=l[b],d=h(f.nTh).addClass(f.sClass),i&&d.appendTo(e),a.oFeatures.bSort&&(d.addClass(f.sSortingClass),!1!==f.bSortable&&(d.attr("tabindex",a.iTabIndex).attr("aria-controls",a.sTableId),Ma(a,f.nTh,b))),f.sTitle!=d[0].innerHTML&&d.html(f.sTitle),Na(a,"header")(a,d,f,n);i&&da(a.aoHeader,g);h(g).find(">tr").attr("role","row");h(g).find(">tr>th, >tr>td").addClass(n.sHeaderTH);h(j).find(">tr>th, >tr>td").addClass(n.sFooterTH);
|
||||
if(null!==j){a=a.aoFooter[0];b=0;for(c=a.length;b<c;b++)f=l[b],f.nTf=a[b].cell,f.sClass&&h(f.nTf).addClass(f.sClass)}}function ea(a,b,c){var d,e,f,g=[],j=[],i=a.aoColumns.length,n;if(b){c===k&&(c=!1);d=0;for(e=b.length;d<e;d++){g[d]=b[d].slice();g[d].nTr=b[d].nTr;for(f=i-1;0<=f;f--)!a.aoColumns[f].bVisible&&!c&&g[d].splice(f,1);j.push([])}d=0;for(e=g.length;d<e;d++){if(a=g[d].nTr)for(;f=a.firstChild;)a.removeChild(f);f=0;for(b=g[d].length;f<b;f++)if(n=i=1,j[d][f]===k){a.appendChild(g[d][f].cell);
|
||||
for(j[d][f]=1;g[d+i]!==k&&g[d][f].cell==g[d+i][f].cell;)j[d+i][f]=1,i++;for(;g[d][f+n]!==k&&g[d][f].cell==g[d][f+n].cell;){for(c=0;c<i;c++)j[d+c][f+n]=1;n++}h(g[d][f].cell).attr("rowspan",i).attr("colspan",n)}}}}function O(a){var b=u(a,"aoPreDrawCallback","preDraw",[a]);if(-1!==h.inArray(!1,b))C(a,!1);else{var b=[],c=0,d=a.asStripeClasses,e=d.length,f=a.oLanguage,g=a.iInitDisplayStart,j="ssp"==y(a),i=a.aiDisplay;a.bDrawing=!0;g!==k&&-1!==g&&(a._iDisplayStart=j?g:g>=a.fnRecordsDisplay()?0:g,a.iInitDisplayStart=
|
||||
-1);var g=a._iDisplayStart,n=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,C(a,!1);else if(j){if(!a.bDestroying&&!lb(a))return}else a.iDraw++;if(0!==i.length){f=j?a.aoData.length:n;for(j=j?0:g;j<f;j++){var l=i[j],q=a.aoData[l];null===q.nTr&&Ha(a,l);l=q.nTr;if(0!==e){var t=d[c%e];q._sRowStripe!=t&&(h(l).removeClass(q._sRowStripe).addClass(t),q._sRowStripe=t)}u(a,"aoRowCallback",null,[l,q._aData,c,j]);b.push(l);c++}}else c=f.sZeroRecords,1==a.iDraw&&"ajax"==y(a)?c=f.sLoadingRecords:
|
||||
f.sEmptyTable&&0===a.fnRecordsTotal()&&(c=f.sEmptyTable),b[0]=h("<tr/>",{"class":e?d[0]:""}).append(h("<td />",{valign:"top",colSpan:aa(a),"class":a.oClasses.sRowEmpty}).html(c))[0];u(a,"aoHeaderCallback","header",[h(a.nTHead).children("tr")[0],Ka(a),g,n,i]);u(a,"aoFooterCallback","footer",[h(a.nTFoot).children("tr")[0],Ka(a),g,n,i]);d=h(a.nTBody);d.children().detach();d.append(h(b));u(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function T(a,b){var c=a.oFeatures,d=c.bFilter;
|
||||
c.bSort&&mb(a);d?fa(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;O(a);a._drawHold=!1}function nb(a){var b=a.oClasses,c=h(a.nTable),c=h("<div/>").insertBefore(c),d=a.oFeatures,e=h("<div/>",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var f=a.sDom.split(""),g,j,i,n,l,q,t=0;t<f.length;t++){g=null;j=f[t];if("<"==j){i=h("<div/>")[0];
|
||||
n=f[t+1];if("'"==n||'"'==n){l="";for(q=2;f[t+q]!=n;)l+=f[t+q],q++;"H"==l?l=b.sJUIHeader:"F"==l&&(l=b.sJUIFooter);-1!=l.indexOf(".")?(n=l.split("."),i.id=n[0].substr(1,n[0].length-1),i.className=n[1]):"#"==l.charAt(0)?i.id=l.substr(1,l.length-1):i.className=l;t+=q}e.append(i);e=h(i)}else if(">"==j)e=e.parent();else if("l"==j&&d.bPaginate&&d.bLengthChange)g=ob(a);else if("f"==j&&d.bFilter)g=pb(a);else if("r"==j&&d.bProcessing)g=qb(a);else if("t"==j)g=rb(a);else if("i"==j&&d.bInfo)g=sb(a);else if("p"==
|
||||
j&&d.bPaginate)g=tb(a);else if(0!==m.ext.feature.length){i=m.ext.feature;q=0;for(n=i.length;q<n;q++)if(j==i[q].cFeature){g=i[q].fnInit(a);break}}g&&(i=a.aanFeatures,i[j]||(i[j]=[]),i[j].push(g),e.append(g))}c.replaceWith(e);a.nHolding=null}function da(a,b){var c=h(b).children("tr"),d,e,f,g,j,i,n,l,q,t;a.splice(0,a.length);f=0;for(i=c.length;f<i;f++)a.push([]);f=0;for(i=c.length;f<i;f++){d=c[f];for(e=d.firstChild;e;){if("TD"==e.nodeName.toUpperCase()||"TH"==e.nodeName.toUpperCase()){l=1*e.getAttribute("colspan");
|
||||
q=1*e.getAttribute("rowspan");l=!l||0===l||1===l?1:l;q=!q||0===q||1===q?1:q;g=0;for(j=a[f];j[g];)g++;n=g;t=1===l?!0:!1;for(j=0;j<l;j++)for(g=0;g<q;g++)a[f+g][n+j]={cell:e,unique:t},a[f+g].nTr=d}e=e.nextSibling}}}function qa(a,b,c){var d=[];c||(c=a.aoHeader,b&&(c=[],da(c,b)));for(var b=0,e=c.length;b<e;b++)for(var f=0,g=c[b].length;f<g;f++)if(c[b][f].unique&&(!d[f]||!a.bSortCellsTop))d[f]=c[b][f].cell;return d}function ra(a,b,c){u(a,"aoServerParams","serverParams",[b]);if(b&&h.isArray(b)){var d={},
|
||||
e=/(.*?)\[\]$/;h.each(b,function(a,b){var c=b.name.match(e);c?(c=c[0],d[c]||(d[c]=[]),d[c].push(b.value)):d[b.name]=b.value});b=d}var f,g=a.ajax,j=a.oInstance,i=function(b){u(a,null,"xhr",[a,b,a.jqXHR]);c(b)};if(h.isPlainObject(g)&&g.data){f=g.data;var n=h.isFunction(f)?f(b,a):f,b=h.isFunction(f)&&n?n:h.extend(!0,b,n);delete g.data}n={data:b,success:function(b){var c=b.error||b.sError;c&&L(a,0,c);a.json=b;i(b)},dataType:"json",cache:!1,type:a.sServerMethod,error:function(b,c){var d=u(a,null,"xhr",
|
||||
[a,null,a.jqXHR]);-1===h.inArray(!0,d)&&("parsererror"==c?L(a,0,"Invalid JSON response",1):4===b.readyState&&L(a,0,"Ajax error",7));C(a,!1)}};a.oAjaxData=b;u(a,null,"preXhr",[a,b]);a.fnServerData?a.fnServerData.call(j,a.sAjaxSource,h.map(b,function(a,b){return{name:b,value:a}}),i,a):a.sAjaxSource||"string"===typeof g?a.jqXHR=h.ajax(h.extend(n,{url:g||a.sAjaxSource})):h.isFunction(g)?a.jqXHR=g.call(j,b,i,a):(a.jqXHR=h.ajax(h.extend(n,g)),g.data=f)}function lb(a){return a.bAjaxDataGet?(a.iDraw++,C(a,
|
||||
!0),ra(a,ub(a),function(b){vb(a,b)}),!1):!0}function ub(a){var b=a.aoColumns,c=b.length,d=a.oFeatures,e=a.oPreviousSearch,f=a.aoPreSearchCols,g,j=[],i,n,l,q=V(a);g=a._iDisplayStart;i=!1!==d.bPaginate?a._iDisplayLength:-1;var k=function(a,b){j.push({name:a,value:b})};k("sEcho",a.iDraw);k("iColumns",c);k("sColumns",G(b,"sName").join(","));k("iDisplayStart",g);k("iDisplayLength",i);var S={draw:a.iDraw,columns:[],order:[],start:g,length:i,search:{value:e.sSearch,regex:e.bRegex}};for(g=0;g<c;g++)n=b[g],
|
||||
l=f[g],i="function"==typeof n.mData?"function":n.mData,S.columns.push({data:i,name:n.sName,searchable:n.bSearchable,orderable:n.bSortable,search:{value:l.sSearch,regex:l.bRegex}}),k("mDataProp_"+g,i),d.bFilter&&(k("sSearch_"+g,l.sSearch),k("bRegex_"+g,l.bRegex),k("bSearchable_"+g,n.bSearchable)),d.bSort&&k("bSortable_"+g,n.bSortable);d.bFilter&&(k("sSearch",e.sSearch),k("bRegex",e.bRegex));d.bSort&&(h.each(q,function(a,b){S.order.push({column:b.col,dir:b.dir});k("iSortCol_"+a,b.col);k("sSortDir_"+
|
||||
a,b.dir)}),k("iSortingCols",q.length));b=m.ext.legacy.ajax;return null===b?a.sAjaxSource?j:S:b?j:S}function vb(a,b){var c=sa(a,b),d=b.sEcho!==k?b.sEcho:b.draw,e=b.iTotalRecords!==k?b.iTotalRecords:b.recordsTotal,f=b.iTotalDisplayRecords!==k?b.iTotalDisplayRecords:b.recordsFiltered;if(d){if(1*d<a.iDraw)return;a.iDraw=1*d}na(a);a._iRecordsTotal=parseInt(e,10);a._iRecordsDisplay=parseInt(f,10);d=0;for(e=c.length;d<e;d++)N(a,c[d]);a.aiDisplay=a.aiDisplayMaster.slice();a.bAjaxDataGet=!1;O(a);a._bInitComplete||
|
||||
ta(a,b);a.bAjaxDataGet=!0;C(a,!1)}function sa(a,b){var c=h.isPlainObject(a.ajax)&&a.ajax.dataSrc!==k?a.ajax.dataSrc:a.sAjaxDataProp;return"data"===c?b.aaData||b[c]:""!==c?Q(c)(b):b}function pb(a){var b=a.oClasses,c=a.sTableId,d=a.oLanguage,e=a.oPreviousSearch,f=a.aanFeatures,g='<input type="search" class="'+b.sFilterInput+'"/>',j=d.sSearch,j=j.match(/_INPUT_/)?j.replace("_INPUT_",g):j+g,b=h("<div/>",{id:!f.f?c+"_filter":null,"class":b.sFilter}).append(h("<label/>").append(j)),f=function(){var b=!this.value?
|
||||
"":this.value;b!=e.sSearch&&(fa(a,{sSearch:b,bRegex:e.bRegex,bSmart:e.bSmart,bCaseInsensitive:e.bCaseInsensitive}),a._iDisplayStart=0,O(a))},g=null!==a.searchDelay?a.searchDelay:"ssp"===y(a)?400:0,i=h("input",b).val(e.sSearch).attr("placeholder",d.sSearchPlaceholder).bind("keyup.DT search.DT input.DT paste.DT cut.DT",g?Oa(f,g):f).bind("keypress.DT",function(a){if(13==a.keyCode)return!1}).attr("aria-controls",c);h(a.nTable).on("search.dt.DT",function(b,c){if(a===c)try{i[0]!==I.activeElement&&i.val(e.sSearch)}catch(d){}});
|
||||
return b[0]}function fa(a,b,c){var d=a.oPreviousSearch,e=a.aoPreSearchCols,f=function(a){d.sSearch=a.sSearch;d.bRegex=a.bRegex;d.bSmart=a.bSmart;d.bCaseInsensitive=a.bCaseInsensitive};Ga(a);if("ssp"!=y(a)){wb(a,b.sSearch,c,b.bEscapeRegex!==k?!b.bEscapeRegex:b.bRegex,b.bSmart,b.bCaseInsensitive);f(b);for(b=0;b<e.length;b++)xb(a,e[b].sSearch,b,e[b].bEscapeRegex!==k?!e[b].bEscapeRegex:e[b].bRegex,e[b].bSmart,e[b].bCaseInsensitive);yb(a)}else f(b);a.bFiltered=!0;u(a,null,"search",[a])}function yb(a){for(var b=
|
||||
m.ext.search,c=a.aiDisplay,d,e,f=0,g=b.length;f<g;f++){for(var j=[],i=0,n=c.length;i<n;i++)e=c[i],d=a.aoData[e],b[f](a,d._aFilterData,e,d._aData,i)&&j.push(e);c.length=0;h.merge(c,j)}}function xb(a,b,c,d,e,f){if(""!==b)for(var g=a.aiDisplay,d=Pa(b,d,e,f),e=g.length-1;0<=e;e--)b=a.aoData[g[e]]._aFilterData[c],d.test(b)||g.splice(e,1)}function wb(a,b,c,d,e,f){var d=Pa(b,d,e,f),e=a.oPreviousSearch.sSearch,f=a.aiDisplayMaster,g;0!==m.ext.search.length&&(c=!0);g=zb(a);if(0>=b.length)a.aiDisplay=f.slice();
|
||||
else{if(g||c||e.length>b.length||0!==b.indexOf(e)||a.bSorted)a.aiDisplay=f.slice();b=a.aiDisplay;for(c=b.length-1;0<=c;c--)d.test(a.aoData[b[c]]._sFilterRow)||b.splice(c,1)}}function Pa(a,b,c,d){a=b?a:Qa(a);c&&(a="^(?=.*?"+h.map(a.match(/"[^"]+"|[^ ]+/g)||[""],function(a){if('"'===a.charAt(0))var b=a.match(/^"(.*)"$/),a=b?b[1]:a;return a.replace('"',"")}).join(")(?=.*?")+").*$");return RegExp(a,d?"i":"")}function zb(a){var b=a.aoColumns,c,d,e,f,g,j,i,h,l=m.ext.type.search;c=!1;d=0;for(f=a.aoData.length;d<
|
||||
f;d++)if(h=a.aoData[d],!h._aFilterData){j=[];e=0;for(g=b.length;e<g;e++)c=b[e],c.bSearchable?(i=B(a,d,e,"filter"),l[c.sType]&&(i=l[c.sType](i)),null===i&&(i=""),"string"!==typeof i&&i.toString&&(i=i.toString())):i="",i.indexOf&&-1!==i.indexOf("&")&&(ua.innerHTML=i,i=Zb?ua.textContent:ua.innerText),i.replace&&(i=i.replace(/[\r\n]/g,"")),j.push(i);h._aFilterData=j;h._sFilterRow=j.join(" ");c=!0}return c}function Ab(a){return{search:a.sSearch,smart:a.bSmart,regex:a.bRegex,caseInsensitive:a.bCaseInsensitive}}
|
||||
function Bb(a){return{sSearch:a.search,bSmart:a.smart,bRegex:a.regex,bCaseInsensitive:a.caseInsensitive}}function sb(a){var b=a.sTableId,c=a.aanFeatures.i,d=h("<div/>",{"class":a.oClasses.sInfo,id:!c?b+"_info":null});c||(a.aoDrawCallback.push({fn:Cb,sName:"information"}),d.attr("role","status").attr("aria-live","polite"),h(a.nTable).attr("aria-describedby",b+"_info"));return d[0]}function Cb(a){var b=a.aanFeatures.i;if(0!==b.length){var c=a.oLanguage,d=a._iDisplayStart+1,e=a.fnDisplayEnd(),f=a.fnRecordsTotal(),
|
||||
g=a.fnRecordsDisplay(),j=g?c.sInfo:c.sInfoEmpty;g!==f&&(j+=" "+c.sInfoFiltered);j+=c.sInfoPostFix;j=Db(a,j);c=c.fnInfoCallback;null!==c&&(j=c.call(a.oInstance,a,d,e,f,g,j));h(b).html(j)}}function Db(a,b){var c=a.fnFormatNumber,d=a._iDisplayStart+1,e=a._iDisplayLength,f=a.fnRecordsDisplay(),g=-1===e;return b.replace(/_START_/g,c.call(a,d)).replace(/_END_/g,c.call(a,a.fnDisplayEnd())).replace(/_MAX_/g,c.call(a,a.fnRecordsTotal())).replace(/_TOTAL_/g,c.call(a,f)).replace(/_PAGE_/g,c.call(a,g?1:Math.ceil(d/
|
||||
e))).replace(/_PAGES_/g,c.call(a,g?1:Math.ceil(f/e)))}function ga(a){var b,c,d=a.iInitDisplayStart,e=a.aoColumns,f;c=a.oFeatures;var g=a.bDeferLoading;if(a.bInitialised){nb(a);kb(a);ea(a,a.aoHeader);ea(a,a.aoFooter);C(a,!0);c.bAutoWidth&&Fa(a);b=0;for(c=e.length;b<c;b++)f=e[b],f.sWidth&&(f.nTh.style.width=x(f.sWidth));u(a,null,"preInit",[a]);T(a);e=y(a);if("ssp"!=e||g)"ajax"==e?ra(a,[],function(c){var f=sa(a,c);for(b=0;b<f.length;b++)N(a,f[b]);a.iInitDisplayStart=d;T(a);C(a,!1);ta(a,c)},a):(C(a,!1),
|
||||
ta(a))}else setTimeout(function(){ga(a)},200)}function ta(a,b){a._bInitComplete=!0;(b||a.oInit.aaData)&&Y(a);u(a,null,"plugin-init",[a,b]);u(a,"aoInitComplete","init",[a,b])}function Ra(a,b){var c=parseInt(b,10);a._iDisplayLength=c;Sa(a);u(a,null,"length",[a,c])}function ob(a){for(var b=a.oClasses,c=a.sTableId,d=a.aLengthMenu,e=h.isArray(d[0]),f=e?d[0]:d,d=e?d[1]:d,e=h("<select/>",{name:c+"_length","aria-controls":c,"class":b.sLengthSelect}),g=0,j=f.length;g<j;g++)e[0][g]=new Option(d[g],f[g]);var i=
|
||||
h("<div><label/></div>").addClass(b.sLength);a.aanFeatures.l||(i[0].id=c+"_length");i.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));h("select",i).val(a._iDisplayLength).bind("change.DT",function(){Ra(a,h(this).val());O(a)});h(a.nTable).bind("length.dt.DT",function(b,c,d){a===c&&h("select",i).val(d)});return i[0]}function tb(a){var b=a.sPaginationType,c=m.ext.pager[b],d="function"===typeof c,e=function(a){O(a)},b=h("<div/>").addClass(a.oClasses.sPaging+b)[0],f=a.aanFeatures;
|
||||
d||c.fnInit(a,b,e);f.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(a){if(d){var b=a._iDisplayStart,i=a._iDisplayLength,h=a.fnRecordsDisplay(),l=-1===i,b=l?0:Math.ceil(b/i),i=l?1:Math.ceil(h/i),h=c(b,i),k,l=0;for(k=f.p.length;l<k;l++)Na(a,"pageButton")(a,f.p[l],l,h,b,i)}else c.fnUpdate(a,e)},sName:"pagination"}));return b}function Ta(a,b,c){var d=a._iDisplayStart,e=a._iDisplayLength,f=a.fnRecordsDisplay();0===f||-1===e?d=0:"number"===typeof b?(d=b*e,d>f&&(d=0)):"first"==b?d=0:
|
||||
"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==b?d+e<f&&(d+=e):"last"==b?d=Math.floor((f-1)/e)*e:L(a,0,"Unknown paging action: "+b,5);b=a._iDisplayStart!==d;a._iDisplayStart=d;b&&(u(a,null,"page",[a]),c&&O(a));return b}function qb(a){return h("<div/>",{id:!a.aanFeatures.r?a.sTableId+"_processing":null,"class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function C(a,b){a.oFeatures.bProcessing&&h(a.aanFeatures.r).css("display",b?"block":"none");u(a,null,"processing",
|
||||
[a,b])}function rb(a){var b=h(a.nTable);b.attr("role","grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY,f=a.oClasses,g=b.children("caption"),j=g.length?g[0]._captionSide:null,i=h(b[0].cloneNode(!1)),n=h(b[0].cloneNode(!1)),l=b.children("tfoot");l.length||(l=null);i=h("<div/>",{"class":f.sScrollWrapper}).append(h("<div/>",{"class":f.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:d?!d?null:x(d):"100%"}).append(h("<div/>",{"class":f.sScrollHeadInner}).css({"box-sizing":"content-box",
|
||||
width:c.sXInner||"100%"}).append(i.removeAttr("id").css("margin-left",0).append("top"===j?g:null).append(b.children("thead"))))).append(h("<div/>",{"class":f.sScrollBody}).css({position:"relative",overflow:"auto",width:!d?null:x(d)}).append(b));l&&i.append(h("<div/>",{"class":f.sScrollFoot}).css({overflow:"hidden",border:0,width:d?!d?null:x(d):"100%"}).append(h("<div/>",{"class":f.sScrollFootInner}).append(n.removeAttr("id").css("margin-left",0).append("bottom"===j?g:null).append(b.children("tfoot")))));
|
||||
var b=i.children(),k=b[0],f=b[1],t=l?b[2]:null;if(d)h(f).on("scroll.DT",function(){var a=this.scrollLeft;k.scrollLeft=a;l&&(t.scrollLeft=a)});h(f).css(e&&c.bCollapse?"max-height":"height",e);a.nScrollHead=k;a.nScrollBody=f;a.nScrollFoot=t;a.aoDrawCallback.push({fn:ka,sName:"scrolling"});return i[0]}function ka(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY,b=b.iBarWidth,f=h(a.nScrollHead),g=f[0].style,j=f.children("div"),i=j[0].style,n=j.children("table"),j=a.nScrollBody,l=h(j),q=j.style,t=h(a.nScrollFoot).children("div"),
|
||||
m=t.children("table"),o=h(a.nTHead),F=h(a.nTable),p=F[0],r=p.style,u=a.nTFoot?h(a.nTFoot):null,Eb=a.oBrowser,Ua=Eb.bScrollOversize,s=G(a.aoColumns,"nTh"),P,v,w,y,z=[],A=[],B=[],C=[],D,E=function(a){a=a.style;a.paddingTop="0";a.paddingBottom="0";a.borderTopWidth="0";a.borderBottomWidth="0";a.height=0};v=j.scrollHeight>j.clientHeight;if(a.scrollBarVis!==v&&a.scrollBarVis!==k)a.scrollBarVis=v,Y(a);else{a.scrollBarVis=v;F.children("thead, tfoot").remove();u&&(w=u.clone().prependTo(F),P=u.find("tr"),w=
|
||||
w.find("tr"));y=o.clone().prependTo(F);o=o.find("tr");v=y.find("tr");y.find("th, td").removeAttr("tabindex");c||(q.width="100%",f[0].style.width="100%");h.each(qa(a,y),function(b,c){D=Z(a,b);c.style.width=a.aoColumns[D].sWidth});u&&J(function(a){a.style.width=""},w);f=F.outerWidth();if(""===c){r.width="100%";if(Ua&&(F.find("tbody").height()>j.offsetHeight||"scroll"==l.css("overflow-y")))r.width=x(F.outerWidth()-b);f=F.outerWidth()}else""!==d&&(r.width=x(d),f=F.outerWidth());J(E,v);J(function(a){B.push(a.innerHTML);
|
||||
z.push(x(h(a).css("width")))},v);J(function(a,b){if(h.inArray(a,s)!==-1)a.style.width=z[b]},o);h(v).height(0);u&&(J(E,w),J(function(a){C.push(a.innerHTML);A.push(x(h(a).css("width")))},w),J(function(a,b){a.style.width=A[b]},P),h(w).height(0));J(function(a,b){a.innerHTML='<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+B[b]+"</div>";a.style.width=z[b]},v);u&&J(function(a,b){a.innerHTML='<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+C[b]+"</div>";a.style.width=
|
||||
A[b]},w);if(F.outerWidth()<f){P=j.scrollHeight>j.offsetHeight||"scroll"==l.css("overflow-y")?f+b:f;if(Ua&&(j.scrollHeight>j.offsetHeight||"scroll"==l.css("overflow-y")))r.width=x(P-b);(""===c||""!==d)&&L(a,1,"Possible column misalignment",6)}else P="100%";q.width=x(P);g.width=x(P);u&&(a.nScrollFoot.style.width=x(P));!e&&Ua&&(q.height=x(p.offsetHeight+b));c=F.outerWidth();n[0].style.width=x(c);i.width=x(c);d=F.height()>j.clientHeight||"scroll"==l.css("overflow-y");e="padding"+(Eb.bScrollbarLeft?"Left":
|
||||
"Right");i[e]=d?b+"px":"0px";u&&(m[0].style.width=x(c),t[0].style.width=x(c),t[0].style[e]=d?b+"px":"0px");F.children("colgroup").insertBefore(F.children("thead"));l.scroll();if((a.bSorted||a.bFiltered)&&!a._drawHold)j.scrollTop=0}}function J(a,b,c){for(var d=0,e=0,f=b.length,g,j;e<f;){g=b[e].firstChild;for(j=c?c[e].firstChild:null;g;)1===g.nodeType&&(c?a(g,j,d):a(g,d),d++),g=g.nextSibling,j=c?j.nextSibling:null;e++}}function Fa(a){var b=a.nTable,c=a.aoColumns,d=a.oScroll,e=d.sY,f=d.sX,g=d.sXInner,
|
||||
j=c.length,i=la(a,"bVisible"),n=h("th",a.nTHead),l=b.getAttribute("width"),k=b.parentNode,t=!1,m,o,p=a.oBrowser,d=p.bScrollOversize;(m=b.style.width)&&-1!==m.indexOf("%")&&(l=m);for(m=0;m<i.length;m++)o=c[i[m]],null!==o.sWidth&&(o.sWidth=Fb(o.sWidthOrig,k),t=!0);if(d||!t&&!f&&!e&&j==aa(a)&&j==n.length)for(m=0;m<j;m++)i=Z(a,m),null!==i&&(c[i].sWidth=x(n.eq(m).width()));else{j=h(b).clone().css("visibility","hidden").removeAttr("id");j.find("tbody tr").remove();var r=h("<tr/>").appendTo(j.find("tbody"));
|
||||
j.find("thead, tfoot").remove();j.append(h(a.nTHead).clone()).append(h(a.nTFoot).clone());j.find("tfoot th, tfoot td").css("width","");n=qa(a,j.find("thead")[0]);for(m=0;m<i.length;m++)o=c[i[m]],n[m].style.width=null!==o.sWidthOrig&&""!==o.sWidthOrig?x(o.sWidthOrig):"",o.sWidthOrig&&f&&h(n[m]).append(h("<div/>").css({width:o.sWidthOrig,margin:0,padding:0,border:0,height:1}));if(a.aoData.length)for(m=0;m<i.length;m++)t=i[m],o=c[t],h(Gb(a,t)).clone(!1).append(o.sContentPadding).appendTo(r);h("[name]",
|
||||
j).removeAttr("name");o=h("<div/>").css(f||e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(j).appendTo(k);f&&g?j.width(g):f?(j.css("width","auto"),j.removeAttr("width"),j.width()<k.clientWidth&&l&&j.width(k.clientWidth)):e?j.width(k.clientWidth):l&&j.width(l);for(m=e=0;m<i.length;m++)k=h(n[m]),g=k.outerWidth()-k.width(),k=p.bBounding?Math.ceil(n[m].getBoundingClientRect().width):k.outerWidth(),e+=k,c[i[m]].sWidth=x(k-g);b.style.width=x(e);o.remove()}l&&(b.style.width=
|
||||
x(l));if((l||f)&&!a._reszEvt)b=function(){h(D).bind("resize.DT-"+a.sInstance,Oa(function(){Y(a)}))},d?setTimeout(b,1E3):b(),a._reszEvt=!0}function Fb(a,b){if(!a)return 0;var c=h("<div/>").css("width",x(a)).appendTo(b||I.body),d=c[0].offsetWidth;c.remove();return d}function Gb(a,b){var c=Hb(a,b);if(0>c)return null;var d=a.aoData[c];return!d.nTr?h("<td/>").html(B(a,c,b,"display"))[0]:d.anCells[b]}function Hb(a,b){for(var c,d=-1,e=-1,f=0,g=a.aoData.length;f<g;f++)c=B(a,f,b,"display")+"",c=c.replace($b,
|
||||
""),c=c.replace(/ /g," "),c.length>d&&(d=c.length,e=f);return e}function x(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function V(a){var b,c,d=[],e=a.aoColumns,f,g,j,i;b=a.aaSortingFixed;c=h.isPlainObject(b);var n=[];f=function(a){a.length&&!h.isArray(a[0])?n.push(a):h.merge(n,a)};h.isArray(b)&&f(b);c&&b.pre&&f(b.pre);f(a.aaSorting);c&&b.post&&f(b.post);for(a=0;a<n.length;a++){i=n[a][0];f=e[i].aDataSort;b=0;for(c=f.length;b<c;b++)g=f[b],j=e[g].sType||
|
||||
"string",n[a]._idx===k&&(n[a]._idx=h.inArray(n[a][1],e[g].asSorting)),d.push({src:i,col:g,dir:n[a][1],index:n[a]._idx,type:j,formatter:m.ext.type.order[j+"-pre"]})}return d}function mb(a){var b,c,d=[],e=m.ext.type.order,f=a.aoData,g=0,j,i=a.aiDisplayMaster,h;Ga(a);h=V(a);b=0;for(c=h.length;b<c;b++)j=h[b],j.formatter&&g++,Ib(a,j.col);if("ssp"!=y(a)&&0!==h.length){b=0;for(c=i.length;b<c;b++)d[i[b]]=b;g===h.length?i.sort(function(a,b){var c,e,g,j,i=h.length,k=f[a]._aSortData,m=f[b]._aSortData;for(g=
|
||||
0;g<i;g++)if(j=h[g],c=k[j.col],e=m[j.col],c=c<e?-1:c>e?1:0,0!==c)return"asc"===j.dir?c:-c;c=d[a];e=d[b];return c<e?-1:c>e?1:0}):i.sort(function(a,b){var c,g,j,i,k=h.length,m=f[a]._aSortData,p=f[b]._aSortData;for(j=0;j<k;j++)if(i=h[j],c=m[i.col],g=p[i.col],i=e[i.type+"-"+i.dir]||e["string-"+i.dir],c=i(c,g),0!==c)return c;c=d[a];g=d[b];return c<g?-1:c>g?1:0})}a.bSorted=!0}function Jb(a){for(var b,c,d=a.aoColumns,e=V(a),a=a.oLanguage.oAria,f=0,g=d.length;f<g;f++){c=d[f];var j=c.asSorting;b=c.sTitle.replace(/<.*?>/g,
|
||||
"");var i=c.nTh;i.removeAttribute("aria-sort");c.bSortable&&(0<e.length&&e[0].col==f?(i.setAttribute("aria-sort","asc"==e[0].dir?"ascending":"descending"),c=j[e[0].index+1]||j[0]):c=j[0],b+="asc"===c?a.sSortAscending:a.sSortDescending);i.setAttribute("aria-label",b)}}function Va(a,b,c,d){var e=a.aaSorting,f=a.aoColumns[b].asSorting,g=function(a,b){var c=a._idx;c===k&&(c=h.inArray(a[1],f));return c+1<f.length?c+1:b?null:0};"number"===typeof e[0]&&(e=a.aaSorting=[e]);c&&a.oFeatures.bSortMulti?(c=h.inArray(b,
|
||||
G(e,"0")),-1!==c?(b=g(e[c],!0),null===b&&1===e.length&&(b=0),null===b?e.splice(c,1):(e[c][1]=f[b],e[c]._idx=b)):(e.push([b,f[0],0]),e[e.length-1]._idx=0)):e.length&&e[0][0]==b?(b=g(e[0]),e.length=1,e[0][1]=f[b],e[0]._idx=b):(e.length=0,e.push([b,f[0]]),e[0]._idx=0);T(a);"function"==typeof d&&d(a)}function Ma(a,b,c,d){var e=a.aoColumns[c];Wa(b,{},function(b){!1!==e.bSortable&&(a.oFeatures.bProcessing?(C(a,!0),setTimeout(function(){Va(a,c,b.shiftKey,d);"ssp"!==y(a)&&C(a,!1)},0)):Va(a,c,b.shiftKey,d))})}
|
||||
function va(a){var b=a.aLastSort,c=a.oClasses.sSortColumn,d=V(a),e=a.oFeatures,f,g;if(e.bSort&&e.bSortClasses){e=0;for(f=b.length;e<f;e++)g=b[e].src,h(G(a.aoData,"anCells",g)).removeClass(c+(2>e?e+1:3));e=0;for(f=d.length;e<f;e++)g=d[e].src,h(G(a.aoData,"anCells",g)).addClass(c+(2>e?e+1:3))}a.aLastSort=d}function Ib(a,b){var c=a.aoColumns[b],d=m.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,$(a,b)));for(var f,g=m.ext.type.order[c.sType+"-pre"],j=0,i=a.aoData.length;j<i;j++)if(c=a.aoData[j],
|
||||
c._aSortData||(c._aSortData=[]),!c._aSortData[b]||d)f=d?e[j]:B(a,j,b,"sort"),c._aSortData[b]=g?g(f):f}function wa(a){if(a.oFeatures.bStateSave&&!a.bDestroying){var b={time:+new Date,start:a._iDisplayStart,length:a._iDisplayLength,order:h.extend(!0,[],a.aaSorting),search:Ab(a.oPreviousSearch),columns:h.map(a.aoColumns,function(b,d){return{visible:b.bVisible,search:Ab(a.aoPreSearchCols[d])}})};u(a,"aoStateSaveParams","stateSaveParams",[a,b]);a.oSavedState=b;a.fnStateSaveCallback.call(a.oInstance,a,
|
||||
b)}}function Kb(a){var b,c,d=a.aoColumns;if(a.oFeatures.bStateSave){var e=a.fnStateLoadCallback.call(a.oInstance,a);if(e&&e.time&&(b=u(a,"aoStateLoadParams","stateLoadParams",[a,e]),-1===h.inArray(!1,b)&&(b=a.iStateDuration,!(0<b&&e.time<+new Date-1E3*b)&&d.length===e.columns.length))){a.oLoadedState=h.extend(!0,{},e);e.start!==k&&(a._iDisplayStart=e.start,a.iInitDisplayStart=e.start);e.length!==k&&(a._iDisplayLength=e.length);e.order!==k&&(a.aaSorting=[],h.each(e.order,function(b,c){a.aaSorting.push(c[0]>=
|
||||
d.length?[0,c[1]]:c)}));e.search!==k&&h.extend(a.oPreviousSearch,Bb(e.search));b=0;for(c=e.columns.length;b<c;b++){var f=e.columns[b];f.visible!==k&&(d[b].bVisible=f.visible);f.search!==k&&h.extend(a.aoPreSearchCols[b],Bb(f.search))}u(a,"aoStateLoaded","stateLoaded",[a,e])}}}function xa(a){var b=m.settings,a=h.inArray(a,G(b,"nTable"));return-1!==a?b[a]:null}function L(a,b,c,d){c="DataTables warning: "+(a?"table id="+a.sTableId+" - ":"")+c;d&&(c+=". For more information about this error, please see http://datatables.net/tn/"+
|
||||
d);if(b)D.console&&console.log&&console.log(c);else if(b=m.ext,b=b.sErrMode||b.errMode,a&&u(a,null,"error",[a,d,c]),"alert"==b)alert(c);else{if("throw"==b)throw Error(c);"function"==typeof b&&b(a,d,c)}}function E(a,b,c,d){h.isArray(c)?h.each(c,function(c,d){h.isArray(d)?E(a,b,d[0],d[1]):E(a,b,d)}):(d===k&&(d=c),b[c]!==k&&(a[d]=b[c]))}function Lb(a,b,c){var d,e;for(e in b)b.hasOwnProperty(e)&&(d=b[e],h.isPlainObject(d)?(h.isPlainObject(a[e])||(a[e]={}),h.extend(!0,a[e],d)):a[e]=c&&"data"!==e&&"aaData"!==
|
||||
e&&h.isArray(d)?d.slice():d);return a}function Wa(a,b,c){h(a).bind("click.DT",b,function(b){a.blur();c(b)}).bind("keypress.DT",b,function(a){13===a.which&&(a.preventDefault(),c(a))}).bind("selectstart.DT",function(){return!1})}function z(a,b,c,d){c&&a[b].push({fn:c,sName:d})}function u(a,b,c,d){var e=[];b&&(e=h.map(a[b].slice().reverse(),function(b){return b.fn.apply(a.oInstance,d)}));null!==c&&(b=h.Event(c+".dt"),h(a.nTable).trigger(b,d),e.push(b.result));return e}function Sa(a){var b=a._iDisplayStart,
|
||||
c=a.fnDisplayEnd(),d=a._iDisplayLength;b>=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function Na(a,b){var c=a.renderer,d=m.ext.renderer[b];return h.isPlainObject(c)&&c[b]?d[c[b]]||d._:"string"===typeof c?d[c]||d._:d._}function y(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function ya(a,b){var c=[],c=Mb.numbers_length,d=Math.floor(c/2);b<=c?c=W(0,b):a<=d?(c=W(0,c-2),c.push("ellipsis"),c.push(b-1)):(a>=b-1-d?c=W(b-(c-2),b):(c=W(a-d+2,a+d-1),c.push("ellipsis"),
|
||||
c.push(b-1)),c.splice(0,0,"ellipsis"),c.splice(0,0,0));c.DT_el="span";return c}function db(a){h.each({num:function(b){return za(b,a)},"num-fmt":function(b){return za(b,a,Xa)},"html-num":function(b){return za(b,a,Aa)},"html-num-fmt":function(b){return za(b,a,Aa,Xa)}},function(b,c){v.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(v.type.search[b+a]=v.type.search.html)})}function Nb(a){return function(){var b=[xa(this[m.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return m.ext.internal[a].apply(this,
|
||||
b)}}var m=function(a){this.$=function(a,b){return this.api(!0).$(a,b)};this._=function(a,b){return this.api(!0).rows(a,b).data()};this.api=function(a){return a?new r(xa(this[v.iApiIndex])):new r(this)};this.fnAddData=function(a,b){var c=this.api(!0),d=h.isArray(a)&&(h.isArray(a[0])||h.isPlainObject(a[0]))?c.rows.add(a):c.row.add(a);(b===k||b)&&c.draw();return d.flatten().toArray()};this.fnAdjustColumnSizing=function(a){var b=this.api(!0).columns.adjust(),c=b.settings()[0],d=c.oScroll;a===k||a?b.draw(!1):
|
||||
(""!==d.sX||""!==d.sY)&&ka(c)};this.fnClearTable=function(a){var b=this.api(!0).clear();(a===k||a)&&b.draw()};this.fnClose=function(a){this.api(!0).row(a).child.hide()};this.fnDeleteRow=function(a,b,c){var d=this.api(!0),a=d.rows(a),e=a.settings()[0],h=e.aoData[a[0][0]];a.remove();b&&b.call(this,e,h);(c===k||c)&&d.draw();return h};this.fnDestroy=function(a){this.api(!0).destroy(a)};this.fnDraw=function(a){this.api(!0).draw(a)};this.fnFilter=function(a,b,c,d,e,h){e=this.api(!0);null===b||b===k?e.search(a,
|
||||
c,d,h):e.column(b).search(a,c,d,h);e.draw()};this.fnGetData=function(a,b){var c=this.api(!0);if(a!==k){var d=a.nodeName?a.nodeName.toLowerCase():"";return b!==k||"td"==d||"th"==d?c.cell(a,b).data():c.row(a).data()||null}return c.data().toArray()};this.fnGetNodes=function(a){var b=this.api(!0);return a!==k?b.row(a).node():b.rows().nodes().flatten().toArray()};this.fnGetPosition=function(a){var b=this.api(!0),c=a.nodeName.toUpperCase();return"TR"==c?b.row(a).index():"TD"==c||"TH"==c?(a=b.cell(a).index(),
|
||||
[a.row,a.columnVisible,a.column]):null};this.fnIsOpen=function(a){return this.api(!0).row(a).child.isShown()};this.fnOpen=function(a,b,c){return this.api(!0).row(a).child(b,c).show().child()[0]};this.fnPageChange=function(a,b){var c=this.api(!0).page(a);(b===k||b)&&c.draw(!1)};this.fnSetColumnVis=function(a,b,c){a=this.api(!0).column(a).visible(b);(c===k||c)&&a.columns.adjust().draw()};this.fnSettings=function(){return xa(this[v.iApiIndex])};this.fnSort=function(a){this.api(!0).order(a).draw()};this.fnSortListener=
|
||||
function(a,b,c){this.api(!0).order.listener(a,b,c)};this.fnUpdate=function(a,b,c,d,e){var h=this.api(!0);c===k||null===c?h.row(b).data(a):h.cell(b,c).data(a);(e===k||e)&&h.columns.adjust();(d===k||d)&&h.draw();return 0};this.fnVersionCheck=v.fnVersionCheck;var b=this,c=a===k,d=this.length;c&&(a={});this.oApi=this.internal=v.internal;for(var e in m.ext.internal)e&&(this[e]=Nb(e));this.each(function(){var e={},e=1<d?Lb(e,a,!0):a,g=0,j,i=this.getAttribute("id"),n=!1,l=m.defaults,q=h(this);if("table"!=
|
||||
this.nodeName.toLowerCase())L(null,0,"Non-table node initialisation ("+this.nodeName+")",2);else{eb(l);fb(l.column);K(l,l,!0);K(l.column,l.column,!0);K(l,h.extend(e,q.data()));var t=m.settings,g=0;for(j=t.length;g<j;g++){var p=t[g];if(p.nTable==this||p.nTHead.parentNode==this||p.nTFoot&&p.nTFoot.parentNode==this){g=e.bRetrieve!==k?e.bRetrieve:l.bRetrieve;if(c||g)return p.oInstance;if(e.bDestroy!==k?e.bDestroy:l.bDestroy){p.oInstance.fnDestroy();break}else{L(p,0,"Cannot reinitialise DataTable",3);
|
||||
return}}if(p.sTableId==this.id){t.splice(g,1);break}}if(null===i||""===i)this.id=i="DataTables_Table_"+m.ext._unique++;var o=h.extend(!0,{},m.models.oSettings,{sDestroyWidth:q[0].style.width,sInstance:i,sTableId:i});o.nTable=this;o.oApi=b.internal;o.oInit=e;t.push(o);o.oInstance=1===b.length?b:q.dataTable();eb(e);e.oLanguage&&Da(e.oLanguage);e.aLengthMenu&&!e.iDisplayLength&&(e.iDisplayLength=h.isArray(e.aLengthMenu[0])?e.aLengthMenu[0][0]:e.aLengthMenu[0]);e=Lb(h.extend(!0,{},l),e);E(o.oFeatures,
|
||||
e,"bPaginate bLengthChange bFilter bSort bSortMulti bInfo bProcessing bAutoWidth bSortClasses bServerSide bDeferRender".split(" "));E(o,e,["asStripeClasses","ajax","fnServerData","fnFormatNumber","sServerMethod","aaSorting","aaSortingFixed","aLengthMenu","sPaginationType","sAjaxSource","sAjaxDataProp","iStateDuration","sDom","bSortCellsTop","iTabIndex","fnStateLoadCallback","fnStateSaveCallback","renderer","searchDelay","rowId",["iCookieDuration","iStateDuration"],["oSearch","oPreviousSearch"],["aoSearchCols",
|
||||
"aoPreSearchCols"],["iDisplayLength","_iDisplayLength"],["bJQueryUI","bJUI"]]);E(o.oScroll,e,[["sScrollX","sX"],["sScrollXInner","sXInner"],["sScrollY","sY"],["bScrollCollapse","bCollapse"]]);E(o.oLanguage,e,"fnInfoCallback");z(o,"aoDrawCallback",e.fnDrawCallback,"user");z(o,"aoServerParams",e.fnServerParams,"user");z(o,"aoStateSaveParams",e.fnStateSaveParams,"user");z(o,"aoStateLoadParams",e.fnStateLoadParams,"user");z(o,"aoStateLoaded",e.fnStateLoaded,"user");z(o,"aoRowCallback",e.fnRowCallback,
|
||||
"user");z(o,"aoRowCreatedCallback",e.fnCreatedRow,"user");z(o,"aoHeaderCallback",e.fnHeaderCallback,"user");z(o,"aoFooterCallback",e.fnFooterCallback,"user");z(o,"aoInitComplete",e.fnInitComplete,"user");z(o,"aoPreDrawCallback",e.fnPreDrawCallback,"user");o.rowIdFn=Q(e.rowId);gb(o);i=o.oClasses;e.bJQueryUI?(h.extend(i,m.ext.oJUIClasses,e.oClasses),e.sDom===l.sDom&&"lfrtip"===l.sDom&&(o.sDom='<"H"lfr>t<"F"ip>'),o.renderer)?h.isPlainObject(o.renderer)&&!o.renderer.header&&(o.renderer.header="jqueryui"):
|
||||
o.renderer="jqueryui":h.extend(i,m.ext.classes,e.oClasses);q.addClass(i.sTable);o.iInitDisplayStart===k&&(o.iInitDisplayStart=e.iDisplayStart,o._iDisplayStart=e.iDisplayStart);null!==e.iDeferLoading&&(o.bDeferLoading=!0,g=h.isArray(e.iDeferLoading),o._iRecordsDisplay=g?e.iDeferLoading[0]:e.iDeferLoading,o._iRecordsTotal=g?e.iDeferLoading[1]:e.iDeferLoading);var r=o.oLanguage;h.extend(!0,r,e.oLanguage);""!==r.sUrl&&(h.ajax({dataType:"json",url:r.sUrl,success:function(a){Da(a);K(l.oLanguage,a);h.extend(true,
|
||||
r,a);ga(o)},error:function(){ga(o)}}),n=!0);null===e.asStripeClasses&&(o.asStripeClasses=[i.sStripeOdd,i.sStripeEven]);var g=o.asStripeClasses,v=q.children("tbody").find("tr").eq(0);-1!==h.inArray(!0,h.map(g,function(a){return v.hasClass(a)}))&&(h("tbody tr",this).removeClass(g.join(" ")),o.asDestroyStripes=g.slice());t=[];g=this.getElementsByTagName("thead");0!==g.length&&(da(o.aoHeader,g[0]),t=qa(o));if(null===e.aoColumns){p=[];g=0;for(j=t.length;g<j;g++)p.push(null)}else p=e.aoColumns;g=0;for(j=
|
||||
p.length;g<j;g++)Ea(o,t?t[g]:null);ib(o,e.aoColumnDefs,p,function(a,b){ja(o,a,b)});if(v.length){var s=function(a,b){return a.getAttribute("data-"+b)!==null?b:null};h(v[0]).children("th, td").each(function(a,b){var c=o.aoColumns[a];if(c.mData===a){var d=s(b,"sort")||s(b,"order"),e=s(b,"filter")||s(b,"search");if(d!==null||e!==null){c.mData={_:a+".display",sort:d!==null?a+".@data-"+d:k,type:d!==null?a+".@data-"+d:k,filter:e!==null?a+".@data-"+e:k};ja(o,a)}}})}var w=o.oFeatures;e.bStateSave&&(w.bStateSave=
|
||||
!0,Kb(o,e),z(o,"aoDrawCallback",wa,"state_save"));if(e.aaSorting===k){t=o.aaSorting;g=0;for(j=t.length;g<j;g++)t[g][1]=o.aoColumns[g].asSorting[0]}va(o);w.bSort&&z(o,"aoDrawCallback",function(){if(o.bSorted){var a=V(o),b={};h.each(a,function(a,c){b[c.src]=c.dir});u(o,null,"order",[o,a,b]);Jb(o)}});z(o,"aoDrawCallback",function(){(o.bSorted||y(o)==="ssp"||w.bDeferRender)&&va(o)},"sc");g=q.children("caption").each(function(){this._captionSide=q.css("caption-side")});j=q.children("thead");0===j.length&&
|
||||
(j=h("<thead/>").appendTo(this));o.nTHead=j[0];j=q.children("tbody");0===j.length&&(j=h("<tbody/>").appendTo(this));o.nTBody=j[0];j=q.children("tfoot");if(0===j.length&&0<g.length&&(""!==o.oScroll.sX||""!==o.oScroll.sY))j=h("<tfoot/>").appendTo(this);0===j.length||0===j.children().length?q.addClass(i.sNoFooter):0<j.length&&(o.nTFoot=j[0],da(o.aoFooter,o.nTFoot));if(e.aaData)for(g=0;g<e.aaData.length;g++)N(o,e.aaData[g]);else(o.bDeferLoading||"dom"==y(o))&&ma(o,h(o.nTBody).children("tr"));o.aiDisplay=
|
||||
o.aiDisplayMaster.slice();o.bInitialised=!0;!1===n&&ga(o)}});b=null;return this},v,r,p,s,Ya={},Ob=/[\r\n]/g,Aa=/<.*?>/g,ac=/^[\w\+\-]/,bc=/[\w\+\-]$/,cc=RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"),Xa=/[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi,M=function(a){return!a||!0===a||"-"===a?!0:!1},Pb=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},Qb=function(a,b){Ya[b]||(Ya[b]=RegExp(Qa(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,
|
||||
"").replace(Ya[b],"."):a},Za=function(a,b,c){var d="string"===typeof a;if(M(a))return!0;b&&d&&(a=Qb(a,b));c&&d&&(a=a.replace(Xa,""));return!isNaN(parseFloat(a))&&isFinite(a)},Rb=function(a,b,c){return M(a)?!0:!(M(a)||"string"===typeof a)?null:Za(a.replace(Aa,""),b,c)?!0:null},G=function(a,b,c){var d=[],e=0,f=a.length;if(c!==k)for(;e<f;e++)a[e]&&a[e][b]&&d.push(a[e][b][c]);else for(;e<f;e++)a[e]&&d.push(a[e][b]);return d},ha=function(a,b,c,d){var e=[],f=0,g=b.length;if(d!==k)for(;f<g;f++)a[b[f]][c]&&
|
||||
e.push(a[b[f]][c][d]);else for(;f<g;f++)e.push(a[b[f]][c]);return e},W=function(a,b){var c=[],d;b===k?(b=0,d=a):(d=b,b=a);for(var e=b;e<d;e++)c.push(e);return c},Sb=function(a){for(var b=[],c=0,d=a.length;c<d;c++)a[c]&&b.push(a[c]);return b},pa=function(a){var b=[],c,d,e=a.length,f,g=0;d=0;a:for(;d<e;d++){c=a[d];for(f=0;f<g;f++)if(b[f]===c)continue a;b.push(c);g++}return b};m.util={throttle:function(a,b){var c=b!==k?b:200,d,e;return function(){var b=this,g=+new Date,h=arguments;d&&g<d+c?(clearTimeout(e),
|
||||
e=setTimeout(function(){d=k;a.apply(b,h)},c)):(d=g,a.apply(b,h))}},escapeRegex:function(a){return a.replace(cc,"\\$1")}};var A=function(a,b,c){a[b]!==k&&(a[c]=a[b])},ba=/\[.*?\]$/,U=/\(\)$/,Qa=m.util.escapeRegex,ua=h("<div>")[0],Zb=ua.textContent!==k,$b=/<.*?>/g,Oa=m.util.throttle,Tb=[],w=Array.prototype,dc=function(a){var b,c,d=m.settings,e=h.map(d,function(a){return a.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase())return b=h.inArray(a,e),-1!==b?[d[b]]:
|
||||
null;if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?c=h(a):a instanceof h&&(c=a)}else return[];if(c)return c.map(function(){b=h.inArray(this,e);return-1!==b?d[b]:null}).toArray()};r=function(a,b){if(!(this instanceof r))return new r(a,b);var c=[],d=function(a){(a=dc(a))&&(c=c.concat(a))};if(h.isArray(a))for(var e=0,f=a.length;e<f;e++)d(a[e]);else d(a);this.context=pa(c);b&&h.merge(this,b);this.selector={rows:null,cols:null,opts:null};r.extend(this,this,Tb)};
|
||||
m.Api=r;h.extend(r.prototype,{any:function(){return 0!==this.count()},concat:w.concat,context:[],count:function(){return this.flatten().length},each:function(a){for(var b=0,c=this.length;b<c;b++)a.call(this,this[b],b,this);return this},eq:function(a){var b=this.context;return b.length>a?new r(b[a],this[a]):null},filter:function(a){var b=[];if(w.filter)b=w.filter.call(this,a,this);else for(var c=0,d=this.length;c<d;c++)a.call(this,this[c],c,this)&&b.push(this[c]);return new r(this.context,b)},flatten:function(){var a=
|
||||
[];return new r(this.context,a.concat.apply(a,this.toArray()))},join:w.join,indexOf:w.indexOf||function(a,b){for(var c=b||0,d=this.length;c<d;c++)if(this[c]===a)return c;return-1},iterator:function(a,b,c,d){var e=[],f,g,h,i,n,l=this.context,m,t,p=this.selector;"string"===typeof a&&(d=c,c=b,b=a,a=!1);g=0;for(h=l.length;g<h;g++){var o=new r(l[g]);if("table"===b)f=c.call(o,l[g],g),f!==k&&e.push(f);else if("columns"===b||"rows"===b)f=c.call(o,l[g],this[g],g),f!==k&&e.push(f);else if("column"===b||"column-rows"===
|
||||
b||"row"===b||"cell"===b){t=this[g];"column-rows"===b&&(m=Ba(l[g],p.opts));i=0;for(n=t.length;i<n;i++)f=t[i],f="cell"===b?c.call(o,l[g],f.row,f.column,g,i):c.call(o,l[g],f,g,i,m),f!==k&&e.push(f)}}return e.length||d?(a=new r(l,a?e.concat.apply([],e):e),b=a.selector,b.rows=p.rows,b.cols=p.cols,b.opts=p.opts,a):this},lastIndexOf:w.lastIndexOf||function(a,b){return this.indexOf.apply(this.toArray.reverse(),arguments)},length:0,map:function(a){var b=[];if(w.map)b=w.map.call(this,a,this);else for(var c=
|
||||
0,d=this.length;c<d;c++)b.push(a.call(this,this[c],c));return new r(this.context,b)},pluck:function(a){return this.map(function(b){return b[a]})},pop:w.pop,push:w.push,reduce:w.reduce||function(a,b){return hb(this,a,b,0,this.length,1)},reduceRight:w.reduceRight||function(a,b){return hb(this,a,b,this.length-1,-1,-1)},reverse:w.reverse,selector:null,shift:w.shift,sort:w.sort,splice:w.splice,toArray:function(){return w.slice.call(this)},to$:function(){return h(this)},toJQuery:function(){return h(this)},
|
||||
unique:function(){return new r(this.context,pa(this))},unshift:w.unshift});r.extend=function(a,b,c){if(c.length&&b&&(b instanceof r||b.__dt_wrapper)){var d,e,f,g=function(a,b,c){return function(){var d=b.apply(a,arguments);r.extend(d,d,c.methodExt);return d}};d=0;for(e=c.length;d<e;d++)f=c[d],b[f.name]="function"===typeof f.val?g(a,f.val,f):h.isPlainObject(f.val)?{}:f.val,b[f.name].__dt_wrapper=!0,r.extend(a,b[f.name],f.propExt)}};r.register=p=function(a,b){if(h.isArray(a))for(var c=0,d=a.length;c<
|
||||
d;c++)r.register(a[c],b);else for(var e=a.split("."),f=Tb,g,j,c=0,d=e.length;c<d;c++){g=(j=-1!==e[c].indexOf("()"))?e[c].replace("()",""):e[c];var i;a:{i=0;for(var n=f.length;i<n;i++)if(f[i].name===g){i=f[i];break a}i=null}i||(i={name:g,val:{},methodExt:[],propExt:[]},f.push(i));c===d-1?i.val=b:f=j?i.methodExt:i.propExt}};r.registerPlural=s=function(a,b,c){r.register(a,c);r.register(b,function(){var a=c.apply(this,arguments);return a===this?this:a instanceof r?a.length?h.isArray(a[0])?new r(a.context,
|
||||
a[0]):a[0]:k:a})};p("tables()",function(a){var b;if(a){b=r;var c=this.context;if("number"===typeof a)a=[c[a]];else var d=h.map(c,function(a){return a.nTable}),a=h(d).filter(a).map(function(){var a=h.inArray(this,d);return c[a]}).toArray();b=new b(a)}else b=this;return b});p("table()",function(a){var a=this.tables(a),b=a.context;return b.length?new r(b[0]):a});s("tables().nodes()","table().node()",function(){return this.iterator("table",function(a){return a.nTable},1)});s("tables().body()","table().body()",
|
||||
function(){return this.iterator("table",function(a){return a.nTBody},1)});s("tables().header()","table().header()",function(){return this.iterator("table",function(a){return a.nTHead},1)});s("tables().footer()","table().footer()",function(){return this.iterator("table",function(a){return a.nTFoot},1)});s("tables().containers()","table().container()",function(){return this.iterator("table",function(a){return a.nTableWrapper},1)});p("draw()",function(a){return this.iterator("table",function(b){"page"===
|
||||
a?O(b):("string"===typeof a&&(a="full-hold"===a?!1:!0),T(b,!1===a))})});p("page()",function(a){return a===k?this.page.info().page:this.iterator("table",function(b){Ta(b,a)})});p("page.info()",function(){if(0===this.context.length)return k;var a=this.context[0],b=a._iDisplayStart,c=a.oFeatures.bPaginate?a._iDisplayLength:-1,d=a.fnRecordsDisplay(),e=-1===c;return{page:e?0:Math.floor(b/c),pages:e?1:Math.ceil(d/c),start:b,end:a.fnDisplayEnd(),length:c,recordsTotal:a.fnRecordsTotal(),recordsDisplay:d,
|
||||
serverSide:"ssp"===y(a)}});p("page.len()",function(a){return a===k?0!==this.context.length?this.context[0]._iDisplayLength:k:this.iterator("table",function(b){Ra(b,a)})});var Ub=function(a,b,c){if(c){var d=new r(a);d.one("draw",function(){c(d.ajax.json())})}if("ssp"==y(a))T(a,b);else{C(a,!0);var e=a.jqXHR;e&&4!==e.readyState&&e.abort();ra(a,[],function(c){na(a);for(var c=sa(a,c),d=0,e=c.length;d<e;d++)N(a,c[d]);T(a,b);C(a,!1)})}};p("ajax.json()",function(){var a=this.context;if(0<a.length)return a[0].json});
|
||||
p("ajax.params()",function(){var a=this.context;if(0<a.length)return a[0].oAjaxData});p("ajax.reload()",function(a,b){return this.iterator("table",function(c){Ub(c,!1===b,a)})});p("ajax.url()",function(a){var b=this.context;if(a===k){if(0===b.length)return k;b=b[0];return b.ajax?h.isPlainObject(b.ajax)?b.ajax.url:b.ajax:b.sAjaxSource}return this.iterator("table",function(b){h.isPlainObject(b.ajax)?b.ajax.url=a:b.ajax=a})});p("ajax.url().load()",function(a,b){return this.iterator("table",function(c){Ub(c,
|
||||
!1===b,a)})});var $a=function(a,b,c,d,e){var f=[],g,j,i,n,l,m;i=typeof b;if(!b||"string"===i||"function"===i||b.length===k)b=[b];i=0;for(n=b.length;i<n;i++){j=b[i]&&b[i].split?b[i].split(","):[b[i]];l=0;for(m=j.length;l<m;l++)(g=c("string"===typeof j[l]?h.trim(j[l]):j[l]))&&g.length&&(f=f.concat(g))}a=v.selector[a];if(a.length){i=0;for(n=a.length;i<n;i++)f=a[i](d,e,f)}return pa(f)},ab=function(a){a||(a={});a.filter&&a.search===k&&(a.search=a.filter);return h.extend({search:"none",order:"current",
|
||||
page:"all"},a)},bb=function(a){for(var b=0,c=a.length;b<c;b++)if(0<a[b].length)return a[0]=a[b],a[0].length=1,a.length=1,a.context=[a.context[b]],a;a.length=0;return a},Ba=function(a,b){var c,d,e,f=[],g=a.aiDisplay;c=a.aiDisplayMaster;var j=b.search;d=b.order;e=b.page;if("ssp"==y(a))return"removed"===j?[]:W(0,c.length);if("current"==e){c=a._iDisplayStart;for(d=a.fnDisplayEnd();c<d;c++)f.push(g[c])}else if("current"==d||"applied"==d)f="none"==j?c.slice():"applied"==j?g.slice():h.map(c,function(a){return-1===
|
||||
h.inArray(a,g)?a:null});else if("index"==d||"original"==d){c=0;for(d=a.aoData.length;c<d;c++)"none"==j?f.push(c):(e=h.inArray(c,g),(-1===e&&"removed"==j||0<=e&&"applied"==j)&&f.push(c))}return f};p("rows()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=ab(b),c=this.iterator("table",function(c){var e=b;return $a("row",a,function(a){var b=Pb(a);if(b!==null&&!e)return[b];var j=Ba(c,e);if(b!==null&&h.inArray(b,j)!==-1)return[b];if(!a)return j;if(typeof a==="function")return h.map(j,function(b){var e=
|
||||
c.aoData[b];return a(b,e._aData,e.nTr)?b:null});b=Sb(ha(c.aoData,j,"nTr"));if(a.nodeName){if(a._DT_RowIndex!==k)return[a._DT_RowIndex];if(a._DT_CellIndex)return[a._DT_CellIndex.row];b=h(a).closest("*[data-dt-row]");return b.length?[b.data("dt-row")]:[]}if(typeof a==="string"&&a.charAt(0)==="#"){j=c.aIds[a.replace(/^#/,"")];if(j!==k)return[j.idx]}return h(b).filter(a).map(function(){return this._DT_RowIndex}).toArray()},c,e)},1);c.selector.rows=a;c.selector.opts=b;return c});p("rows().nodes()",function(){return this.iterator("row",
|
||||
function(a,b){return a.aoData[b].nTr||k},1)});p("rows().data()",function(){return this.iterator(!0,"rows",function(a,b){return ha(a.aoData,b,"_aData")},1)});s("rows().cache()","row().cache()",function(a){return this.iterator("row",function(b,c){var d=b.aoData[c];return"search"===a?d._aFilterData:d._aSortData},1)});s("rows().invalidate()","row().invalidate()",function(a){return this.iterator("row",function(b,c){ca(b,c,a)})});s("rows().indexes()","row().index()",function(){return this.iterator("row",
|
||||
function(a,b){return b},1)});s("rows().ids()","row().id()",function(a){for(var b=[],c=this.context,d=0,e=c.length;d<e;d++)for(var f=0,g=this[d].length;f<g;f++){var h=c[d].rowIdFn(c[d].aoData[this[d][f]]._aData);b.push((!0===a?"#":"")+h)}return new r(c,b)});s("rows().remove()","row().remove()",function(){var a=this;this.iterator("row",function(b,c,d){var e=b.aoData,f=e[c],g,h,i,n,l;e.splice(c,1);g=0;for(h=e.length;g<h;g++)if(i=e[g],l=i.anCells,null!==i.nTr&&(i.nTr._DT_RowIndex=g),null!==l){i=0;for(n=
|
||||
l.length;i<n;i++)l[i]._DT_CellIndex.row=g}oa(b.aiDisplayMaster,c);oa(b.aiDisplay,c);oa(a[d],c,!1);Sa(b);c=b.rowIdFn(f._aData);c!==k&&delete b.aIds[c]});this.iterator("table",function(a){for(var c=0,d=a.aoData.length;c<d;c++)a.aoData[c].idx=c});return this});p("rows.add()",function(a){var b=this.iterator("table",function(b){var c,f,g,h=[];f=0;for(g=a.length;f<g;f++)c=a[f],c.nodeName&&"TR"===c.nodeName.toUpperCase()?h.push(ma(b,c)[0]):h.push(N(b,c));return h},1),c=this.rows(-1);c.pop();h.merge(c,b);
|
||||
return c});p("row()",function(a,b){return bb(this.rows(a,b))});p("row().data()",function(a){var b=this.context;if(a===k)return b.length&&this.length?b[0].aoData[this[0]]._aData:k;b[0].aoData[this[0]]._aData=a;ca(b[0],this[0],"data");return this});p("row().node()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]].nTr||null:null});p("row.add()",function(a){a instanceof h&&a.length&&(a=a[0]);var b=this.iterator("table",function(b){return a.nodeName&&"TR"===a.nodeName.toUpperCase()?
|
||||
ma(b,a)[0]:N(b,a)});return this.row(b[0])});var cb=function(a,b){var c=a.context;if(c.length&&(c=c[0].aoData[b!==k?b:a[0]])&&c._details)c._details.remove(),c._detailsShow=k,c._details=k},Vb=function(a,b){var c=a.context;if(c.length&&a.length){var d=c[0].aoData[a[0]];if(d._details){(d._detailsShow=b)?d._details.insertAfter(d.nTr):d._details.detach();var e=c[0],f=new r(e),g=e.aoData;f.off("draw.dt.DT_details column-visibility.dt.DT_details destroy.dt.DT_details");0<G(g,"_details").length&&(f.on("draw.dt.DT_details",
|
||||
function(a,b){e===b&&f.rows({page:"current"}).eq(0).each(function(a){a=g[a];a._detailsShow&&a._details.insertAfter(a.nTr)})}),f.on("column-visibility.dt.DT_details",function(a,b){if(e===b)for(var c,d=aa(b),f=0,h=g.length;f<h;f++)c=g[f],c._details&&c._details.children("td[colspan]").attr("colspan",d)}),f.on("destroy.dt.DT_details",function(a,b){if(e===b)for(var c=0,d=g.length;c<d;c++)g[c]._details&&cb(f,c)}))}}};p("row().child()",function(a,b){var c=this.context;if(a===k)return c.length&&this.length?
|
||||
c[0].aoData[this[0]]._details:k;if(!0===a)this.child.show();else if(!1===a)cb(this);else if(c.length&&this.length){var d=c[0],c=c[0].aoData[this[0]],e=[],f=function(a,b){if(h.isArray(a)||a instanceof h)for(var c=0,k=a.length;c<k;c++)f(a[c],b);else a.nodeName&&"tr"===a.nodeName.toLowerCase()?e.push(a):(c=h("<tr><td/></tr>").addClass(b),h("td",c).addClass(b).html(a)[0].colSpan=aa(d),e.push(c[0]))};f(a,b);c._details&&c._details.remove();c._details=h(e);c._detailsShow&&c._details.insertAfter(c.nTr)}return this});
|
||||
p(["row().child.show()","row().child().show()"],function(){Vb(this,!0);return this});p(["row().child.hide()","row().child().hide()"],function(){Vb(this,!1);return this});p(["row().child.remove()","row().child().remove()"],function(){cb(this);return this});p("row().child.isShown()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]]._detailsShow||!1:!1});var ec=/^(.+):(name|visIdx|visible)$/,Wb=function(a,b,c,d,e){for(var c=[],d=0,f=e.length;d<f;d++)c.push(B(a,e[d],b));
|
||||
return c};p("columns()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=ab(b),c=this.iterator("table",function(c){var e=a,f=b,g=c.aoColumns,j=G(g,"sName"),i=G(g,"nTh");return $a("column",e,function(a){var b=Pb(a);if(a==="")return W(g.length);if(b!==null)return[b>=0?b:g.length+b];if(typeof a==="function"){var e=Ba(c,f);return h.map(g,function(b,f){return a(f,Wb(c,f,0,0,e),i[f])?f:null})}var k=typeof a==="string"?a.match(ec):"";if(k)switch(k[2]){case "visIdx":case "visible":b=parseInt(k[1],
|
||||
10);if(b<0){var m=h.map(g,function(a,b){return a.bVisible?b:null});return[m[m.length+b]]}return[Z(c,b)];case "name":return h.map(j,function(a,b){return a===k[1]?b:null});default:return[]}if(a.nodeName&&a._DT_CellIndex)return[a._DT_CellIndex.column];b=h(i).filter(a).map(function(){return h.inArray(this,i)}).toArray();if(b.length||!a.nodeName)return b;b=h(a).closest("*[data-dt-column]");return b.length?[b.data("dt-column")]:[]},c,f)},1);c.selector.cols=a;c.selector.opts=b;return c});s("columns().header()",
|
||||
"column().header()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTh},1)});s("columns().footer()","column().footer()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTf},1)});s("columns().data()","column().data()",function(){return this.iterator("column-rows",Wb,1)});s("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},1)});s("columns().cache()","column().cache()",
|
||||
function(a){return this.iterator("column-rows",function(b,c,d,e,f){return ha(b.aoData,f,"search"===a?"_aFilterData":"_aSortData",c)},1)});s("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,d,e){return ha(a.aoData,e,"anCells",b)},1)});s("columns().visible()","column().visible()",function(a,b){var c=this.iterator("column",function(b,c){if(a===k)return b.aoColumns[c].bVisible;var f=b.aoColumns,g=f[c],j=b.aoData,i,n,l;if(a!==k&&g.bVisible!==a){if(a){var m=
|
||||
h.inArray(!0,G(f,"bVisible"),c+1);i=0;for(n=j.length;i<n;i++)l=j[i].nTr,f=j[i].anCells,l&&l.insertBefore(f[c],f[m]||null)}else h(G(b.aoData,"anCells",c)).detach();g.bVisible=a;ea(b,b.aoHeader);ea(b,b.aoFooter);wa(b)}});a!==k&&(this.iterator("column",function(c,e){u(c,null,"column-visibility",[c,e,a,b])}),(b===k||b)&&this.columns.adjust());return c});s("columns().indexes()","column().index()",function(a){return this.iterator("column",function(b,c){return"visible"===a?$(b,c):c},1)});p("columns.adjust()",
|
||||
function(){return this.iterator("table",function(a){Y(a)},1)});p("column.index()",function(a,b){if(0!==this.context.length){var c=this.context[0];if("fromVisible"===a||"toData"===a)return Z(c,b);if("fromData"===a||"toVisible"===a)return $(c,b)}});p("column()",function(a,b){return bb(this.columns(a,b))});p("cells()",function(a,b,c){h.isPlainObject(a)&&(a.row===k?(c=a,a=null):(c=b,b=null));h.isPlainObject(b)&&(c=b,b=null);if(null===b||b===k)return this.iterator("table",function(b){var d=a,e=ab(c),f=
|
||||
b.aoData,g=Ba(b,e),j=Sb(ha(f,g,"anCells")),i=h([].concat.apply([],j)),l,n=b.aoColumns.length,m,p,r,u,v,s;return $a("cell",d,function(a){var c=typeof a==="function";if(a===null||a===k||c){m=[];p=0;for(r=g.length;p<r;p++){l=g[p];for(u=0;u<n;u++){v={row:l,column:u};if(c){s=f[l];a(v,B(b,l,u),s.anCells?s.anCells[u]:null)&&m.push(v)}else m.push(v)}}return m}if(h.isPlainObject(a))return[a];c=i.filter(a).map(function(a,b){return{row:b._DT_CellIndex.row,column:b._DT_CellIndex.column}}).toArray();if(c.length||
|
||||
!a.nodeName)return c;s=h(a).closest("*[data-dt-row]");return s.length?[{row:s.data("dt-row"),column:s.data("dt-column")}]:[]},b,e)});var d=this.columns(b,c),e=this.rows(a,c),f,g,j,i,n,l=this.iterator("table",function(a,b){f=[];g=0;for(j=e[b].length;g<j;g++){i=0;for(n=d[b].length;i<n;i++)f.push({row:e[b][g],column:d[b][i]})}return f},1);h.extend(l.selector,{cols:b,rows:a,opts:c});return l});s("cells().nodes()","cell().node()",function(){return this.iterator("cell",function(a,b,c){return(a=a.aoData[b])&&
|
||||
a.anCells?a.anCells[c]:k},1)});p("cells().data()",function(){return this.iterator("cell",function(a,b,c){return B(a,b,c)},1)});s("cells().cache()","cell().cache()",function(a){a="search"===a?"_aFilterData":"_aSortData";return this.iterator("cell",function(b,c,d){return b.aoData[c][a][d]},1)});s("cells().render()","cell().render()",function(a){return this.iterator("cell",function(b,c,d){return B(b,c,d,a)},1)});s("cells().indexes()","cell().index()",function(){return this.iterator("cell",function(a,
|
||||
b,c){return{row:b,column:c,columnVisible:$(a,c)}},1)});s("cells().invalidate()","cell().invalidate()",function(a){return this.iterator("cell",function(b,c,d){ca(b,c,a,d)})});p("cell()",function(a,b,c){return bb(this.cells(a,b,c))});p("cell().data()",function(a){var b=this.context,c=this[0];if(a===k)return b.length&&c.length?B(b[0],c[0].row,c[0].column):k;jb(b[0],c[0].row,c[0].column,a);ca(b[0],c[0].row,"data",c[0].column);return this});p("order()",function(a,b){var c=this.context;if(a===k)return 0!==
|
||||
c.length?c[0].aaSorting:k;"number"===typeof a?a=[[a,b]]:a.length&&!h.isArray(a[0])&&(a=Array.prototype.slice.call(arguments));return this.iterator("table",function(b){b.aaSorting=a.slice()})});p("order.listener()",function(a,b,c){return this.iterator("table",function(d){Ma(d,a,b,c)})});p("order.fixed()",function(a){if(!a){var b=this.context,b=b.length?b[0].aaSortingFixed:k;return h.isArray(b)?{pre:b}:b}return this.iterator("table",function(b){b.aaSortingFixed=h.extend(!0,{},a)})});p(["columns().order()",
|
||||
"column().order()"],function(a){var b=this;return this.iterator("table",function(c,d){var e=[];h.each(b[d],function(b,c){e.push([c,a])});c.aaSorting=e})});p("search()",function(a,b,c,d){var e=this.context;return a===k?0!==e.length?e[0].oPreviousSearch.sSearch:k:this.iterator("table",function(e){e.oFeatures.bFilter&&fa(e,h.extend({},e.oPreviousSearch,{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===d?!0:d}),1)})});s("columns().search()","column().search()",function(a,
|
||||
b,c,d){return this.iterator("column",function(e,f){var g=e.aoPreSearchCols;if(a===k)return g[f].sSearch;e.oFeatures.bFilter&&(h.extend(g[f],{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===d?!0:d}),fa(e,e.oPreviousSearch,1))})});p("state()",function(){return this.context.length?this.context[0].oSavedState:null});p("state.clear()",function(){return this.iterator("table",function(a){a.fnStateSaveCallback.call(a.oInstance,a,{})})});p("state.loaded()",function(){return this.context.length?
|
||||
this.context[0].oLoadedState:null});p("state.save()",function(){return this.iterator("table",function(a){wa(a)})});m.versionCheck=m.fnVersionCheck=function(a){for(var b=m.version.split("."),a=a.split("."),c,d,e=0,f=a.length;e<f;e++)if(c=parseInt(b[e],10)||0,d=parseInt(a[e],10)||0,c!==d)return c>d;return!0};m.isDataTable=m.fnIsDataTable=function(a){var b=h(a).get(0),c=!1;h.each(m.settings,function(a,e){var f=e.nScrollHead?h("table",e.nScrollHead)[0]:null,g=e.nScrollFoot?h("table",e.nScrollFoot)[0]:
|
||||
null;if(e.nTable===b||f===b||g===b)c=!0});return c};m.tables=m.fnTables=function(a){var b=!1;h.isPlainObject(a)&&(b=a.api,a=a.visible);var c=h.map(m.settings,function(b){if(!a||a&&h(b.nTable).is(":visible"))return b.nTable});return b?new r(c):c};m.camelToHungarian=K;p("$()",function(a,b){var c=this.rows(b).nodes(),c=h(c);return h([].concat(c.filter(a).toArray(),c.find(a).toArray()))});h.each(["on","one","off"],function(a,b){p(b+"()",function(){var a=Array.prototype.slice.call(arguments);a[0].match(/\.dt\b/)||
|
||||
(a[0]+=".dt");var d=h(this.tables().nodes());d[b].apply(d,a);return this})});p("clear()",function(){return this.iterator("table",function(a){na(a)})});p("settings()",function(){return new r(this.context,this.context)});p("init()",function(){var a=this.context;return a.length?a[0].oInit:null});p("data()",function(){return this.iterator("table",function(a){return G(a.aoData,"_aData")}).flatten()});p("destroy()",function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,
|
||||
d=b.oClasses,e=b.nTable,f=b.nTBody,g=b.nTHead,j=b.nTFoot,i=h(e),f=h(f),k=h(b.nTableWrapper),l=h.map(b.aoData,function(a){return a.nTr}),p;b.bDestroying=!0;u(b,"aoDestroyCallback","destroy",[b]);a||(new r(b)).columns().visible(!0);k.unbind(".DT").find(":not(tbody *)").unbind(".DT");h(D).unbind(".DT-"+b.sInstance);e!=g.parentNode&&(i.children("thead").detach(),i.append(g));j&&e!=j.parentNode&&(i.children("tfoot").detach(),i.append(j));b.aaSorting=[];b.aaSortingFixed=[];va(b);h(l).removeClass(b.asStripeClasses.join(" "));
|
||||
h("th, td",g).removeClass(d.sSortable+" "+d.sSortableAsc+" "+d.sSortableDesc+" "+d.sSortableNone);b.bJUI&&(h("th span."+d.sSortIcon+", td span."+d.sSortIcon,g).detach(),h("th, td",g).each(function(){var a=h("div."+d.sSortJUIWrapper,this);h(this).append(a.contents());a.detach()}));f.children().detach();f.append(l);g=a?"remove":"detach";i[g]();k[g]();!a&&c&&(c.insertBefore(e,b.nTableReinsertBefore),i.css("width",b.sDestroyWidth).removeClass(d.sTable),(p=b.asDestroyStripes.length)&&f.children().each(function(a){h(this).addClass(b.asDestroyStripes[a%
|
||||
p])}));c=h.inArray(b,m.settings);-1!==c&&m.settings.splice(c,1)})});h.each(["column","row","cell"],function(a,b){p(b+"s().every()",function(a){var d=this.selector.opts,e=this;return this.iterator(b,function(f,g,h,i,n){a.call(e[b](g,"cell"===b?h:d,"cell"===b?d:k),g,h,i,n)})})});p("i18n()",function(a,b,c){var d=this.context[0],a=Q(a)(d.oLanguage);a===k&&(a=b);c!==k&&h.isPlainObject(a)&&(a=a[c]!==k?a[c]:a._);return a.replace("%d",c)});m.version="1.10.12";m.settings=[];m.models={};m.models.oSearch={bCaseInsensitive:!0,
|
||||
sSearch:"",bRegex:!1,bSmart:!0};m.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,idx:-1};m.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,
|
||||
sTitle:null,sType:null,sWidth:null,sWidthOrig:null};m.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bJQueryUI:!1,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,
|
||||
fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===
|
||||
a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",
|
||||
sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:h.extend({},m.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",
|
||||
renderer:null,rowId:"DT_RowId"};X(m.defaults);m.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};X(m.defaults.column);m.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,
|
||||
bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],
|
||||
aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:!0,jqXHR:null,json:k,oAjaxData:k,
|
||||
fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,bJUI:null,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==y(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==y(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=
|
||||
this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=this.oFeatures,f=e.bPaginate;return e.bServerSide?!1===f||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!f||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};m.ext=v={buttons:{},classes:{},builder:"-source-",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},
|
||||
header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:m.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:m.version};h.extend(v,{afnFiltering:v.search,aTypes:v.type.detect,ofnSearch:v.type.search,oSort:v.type.order,afnSortData:v.order,aoFeatures:v.feature,oApi:v.internal,oStdClasses:v.classes,oPagination:v.pager});h.extend(m.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",
|
||||
sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",
|
||||
sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var Ca="",Ca="",H=Ca+"ui-state-default",ia=Ca+"css_right ui-icon ui-icon-",Xb=Ca+"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix";h.extend(m.ext.oJUIClasses,
|
||||
m.ext.classes,{sPageButton:"fg-button ui-button "+H,sPageButtonActive:"ui-state-disabled",sPageButtonDisabled:"ui-state-disabled",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",sSortAsc:H+" sorting_asc",sSortDesc:H+" sorting_desc",sSortable:H+" sorting",sSortableAsc:H+" sorting_asc_disabled",sSortableDesc:H+" sorting_desc_disabled",sSortableNone:H+" sorting_disabled",sSortJUIAsc:ia+"triangle-1-n",sSortJUIDesc:ia+"triangle-1-s",sSortJUI:ia+"carat-2-n-s",
|
||||
sSortJUIAscAllowed:ia+"carat-1-n",sSortJUIDescAllowed:ia+"carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",sSortIcon:"DataTables_sort_icon",sScrollHead:"dataTables_scrollHead "+H,sScrollFoot:"dataTables_scrollFoot "+H,sHeaderTH:H,sFooterTH:H,sJUIHeader:Xb+" ui-corner-tl ui-corner-tr",sJUIFooter:Xb+" ui-corner-bl ui-corner-br"});var Mb=m.ext.pager;h.extend(Mb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},numbers:function(a,b){return[ya(a,
|
||||
b)]},simple_numbers:function(a,b){return["previous",ya(a,b),"next"]},full_numbers:function(a,b){return["first","previous",ya(a,b),"next","last"]},_numbers:ya,numbers_length:7});h.extend(!0,m.ext.renderer,{pageButton:{_:function(a,b,c,d,e,f){var g=a.oClasses,j=a.oLanguage.oPaginate,i=a.oLanguage.oAria.paginate||{},k,l,m=0,p=function(b,d){var o,r,u,s,v=function(b){Ta(a,b.data.action,true)};o=0;for(r=d.length;o<r;o++){s=d[o];if(h.isArray(s)){u=h("<"+(s.DT_el||"div")+"/>").appendTo(b);p(u,s)}else{k=null;
|
||||
l="";switch(s){case "ellipsis":b.append('<span class="ellipsis">…</span>');break;case "first":k=j.sFirst;l=s+(e>0?"":" "+g.sPageButtonDisabled);break;case "previous":k=j.sPrevious;l=s+(e>0?"":" "+g.sPageButtonDisabled);break;case "next":k=j.sNext;l=s+(e<f-1?"":" "+g.sPageButtonDisabled);break;case "last":k=j.sLast;l=s+(e<f-1?"":" "+g.sPageButtonDisabled);break;default:k=s+1;l=e===s?g.sPageButtonActive:""}if(k!==null){u=h("<a>",{"class":g.sPageButton+" "+l,"aria-controls":a.sTableId,"aria-label":i[s],
|
||||
"data-dt-idx":m,tabindex:a.iTabIndex,id:c===0&&typeof s==="string"?a.sTableId+"_"+s:null}).html(k).appendTo(b);Wa(u,{action:s},v);m++}}}},r;try{r=h(b).find(I.activeElement).data("dt-idx")}catch(o){}p(h(b).empty(),d);r&&h(b).find("[data-dt-idx="+r+"]").focus()}}});h.extend(m.ext.type.detect,[function(a,b){var c=b.oLanguage.sDecimal;return Za(a,c)?"num"+c:null},function(a){if(a&&!(a instanceof Date)&&(!ac.test(a)||!bc.test(a)))return null;var b=Date.parse(a);return null!==b&&!isNaN(b)||M(a)?"date":
|
||||
null},function(a,b){var c=b.oLanguage.sDecimal;return Za(a,c,!0)?"num-fmt"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c)?"html-num"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c,!0)?"html-num-fmt"+c:null},function(a){return M(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);h.extend(m.ext.type.search,{html:function(a){return M(a)?a:"string"===typeof a?a.replace(Ob," ").replace(Aa,""):""},string:function(a){return M(a)?a:"string"===typeof a?a.replace(Ob,
|
||||
" "):a}});var za=function(a,b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=Qb(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};h.extend(v.type.order,{"date-pre":function(a){return Date.parse(a)||0},"html-pre":function(a){return M(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return M(a)?"":"string"===typeof a?a.toLowerCase():!a.toString?"":a.toString()},"string-asc":function(a,b){return a<b?-1:a>b?1:0},"string-desc":function(a,
|
||||
b){return a<b?1:a>b?-1:0}});db("");h.extend(!0,m.ext.renderer,{header:{_:function(a,b,c,d){h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(c.sSortingClass+" "+d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass)}})},jqueryui:function(a,b,c,d){h("<div/>").addClass(d.sSortJUIWrapper).append(b.contents()).append(h("<span/>").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);h(a.nTable).on("order.dt.DT",function(e,
|
||||
f,g,h){if(a===f){e=c.idx;b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass);b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass(h[e]=="asc"?d.sSortJUIAsc:h[e]=="desc"?d.sSortJUIDesc:c.sSortingClassJUI)}})}}});var Yb=function(a){return"string"===typeof a?a.replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""):a};m.render={number:function(a,
|
||||
b,c,d,e){return{display:function(f){if("number"!==typeof f&&"string"!==typeof f)return f;var g=0>f?"-":"",h=parseFloat(f);if(isNaN(h))return Yb(f);f=Math.abs(h);h=parseInt(f,10);f=c?b+(f-h).toFixed(c).substring(2):"";return g+(d||"")+h.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+f+(e||"")}}},text:function(){return{display:Yb}}};h.extend(m.ext.internal,{_fnExternApiFunc:Nb,_fnBuildAjax:ra,_fnAjaxUpdate:lb,_fnAjaxParameters:ub,_fnAjaxUpdateDraw:vb,_fnAjaxDataSrc:sa,_fnAddColumn:Ea,_fnColumnOptions:ja,
|
||||
_fnAdjustColumnSizing:Y,_fnVisibleToColumnIndex:Z,_fnColumnIndexToVisible:$,_fnVisbleColumns:aa,_fnGetColumns:la,_fnColumnTypes:Ga,_fnApplyColumnDefs:ib,_fnHungarianMap:X,_fnCamelToHungarian:K,_fnLanguageCompat:Da,_fnBrowserDetect:gb,_fnAddData:N,_fnAddTr:ma,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==k?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return h.inArray(c,a.aoData[b].anCells)},_fnGetCellData:B,_fnSetCellData:jb,_fnSplitObjNotation:Ja,_fnGetObjectDataFn:Q,_fnSetObjectDataFn:R,
|
||||
_fnGetDataMaster:Ka,_fnClearTable:na,_fnDeleteIndex:oa,_fnInvalidate:ca,_fnGetRowElements:Ia,_fnCreateTr:Ha,_fnBuildHead:kb,_fnDrawHead:ea,_fnDraw:O,_fnReDraw:T,_fnAddOptionsHtml:nb,_fnDetectHeader:da,_fnGetUniqueThs:qa,_fnFeatureHtmlFilter:pb,_fnFilterComplete:fa,_fnFilterCustom:yb,_fnFilterColumn:xb,_fnFilter:wb,_fnFilterCreateSearch:Pa,_fnEscapeRegex:Qa,_fnFilterData:zb,_fnFeatureHtmlInfo:sb,_fnUpdateInfo:Cb,_fnInfoMacros:Db,_fnInitialise:ga,_fnInitComplete:ta,_fnLengthChange:Ra,_fnFeatureHtmlLength:ob,
|
||||
_fnFeatureHtmlPaginate:tb,_fnPageChange:Ta,_fnFeatureHtmlProcessing:qb,_fnProcessingDisplay:C,_fnFeatureHtmlTable:rb,_fnScrollDraw:ka,_fnApplyToChildren:J,_fnCalculateColumnWidths:Fa,_fnThrottle:Oa,_fnConvertToWidth:Fb,_fnGetWidestNode:Gb,_fnGetMaxLenString:Hb,_fnStringToCss:x,_fnSortFlatten:V,_fnSort:mb,_fnSortAria:Jb,_fnSortListener:Va,_fnSortAttachListener:Ma,_fnSortingClasses:va,_fnSortData:Ib,_fnSaveState:wa,_fnLoadState:Kb,_fnSettingsFromNode:xa,_fnLog:L,_fnMap:E,_fnBindAction:Wa,_fnCallbackReg:z,
|
||||
_fnCallbackFire:u,_fnLengthOverflow:Sa,_fnRenderer:Na,_fnDataSource:y,_fnRowAttributes:La,_fnCalculateEnd:function(){}});h.fn.dataTable=m;m.$=h;h.fn.dataTableSettings=m.settings;h.fn.dataTableExt=m.ext;h.fn.DataTable=function(a){return h(this).dataTable(a).api()};h.each(m,function(a,b){h.fn.DataTable[a]=b});return h.fn.dataTable});
|
||||
338
v2realbot/static/js/jquery.serializejson.js
Normal file
338
v2realbot/static/js/jquery.serializejson.js
Normal file
@ -0,0 +1,338 @@
|
||||
/*!
|
||||
SerializeJSON jQuery plugin.
|
||||
https://github.com/marioizquierdo/jquery.serializeJSON
|
||||
version 3.2.1 (Feb, 2021)
|
||||
|
||||
Copyright (c) 2012-2021 Mario Izquierdo
|
||||
Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
|
||||
and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
|
||||
*/
|
||||
(function (factory) {
|
||||
/* global define, require, module */
|
||||
if (typeof define === "function" && define.amd) { // AMD. Register as an anonymous module.
|
||||
define(["jquery"], factory);
|
||||
} else if (typeof exports === "object") { // Node/CommonJS
|
||||
var jQuery = require("jquery");
|
||||
module.exports = factory(jQuery);
|
||||
} else { // Browser globals (zepto supported)
|
||||
factory(window.jQuery || window.Zepto || window.$); // Zepto supported on browsers as well
|
||||
}
|
||||
|
||||
}(function ($) {
|
||||
"use strict";
|
||||
|
||||
var rCRLF = /\r?\n/g;
|
||||
var rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i;
|
||||
var rsubmittable = /^(?:input|select|textarea|keygen)/i;
|
||||
var rcheckableType = /^(?:checkbox|radio)$/i;
|
||||
|
||||
$.fn.serializeJSON = function (options) {
|
||||
var f = $.serializeJSON;
|
||||
var $form = this; // NOTE: the set of matched elements is most likely a form, but it could also be a group of inputs
|
||||
var opts = f.setupOpts(options); // validate options and apply defaults
|
||||
var typeFunctions = $.extend({}, opts.defaultTypes, opts.customTypes);
|
||||
|
||||
// Make a list with {name, value, el} for each input element
|
||||
var serializedArray = f.serializeArray($form, opts);
|
||||
|
||||
// Convert the serializedArray into a serializedObject with nested keys
|
||||
var serializedObject = {};
|
||||
$.each(serializedArray, function (_i, obj) {
|
||||
|
||||
var nameSansType = obj.name;
|
||||
var type = $(obj.el).attr("data-value-type");
|
||||
|
||||
if (!type && !opts.disableColonTypes) { // try getting the type from the input name
|
||||
var p = f.splitType(obj.name); // "foo:string" => ["foo", "string"]
|
||||
nameSansType = p[0];
|
||||
type = p[1];
|
||||
}
|
||||
if (type === "skip") {
|
||||
return; // ignore fields with type skip
|
||||
}
|
||||
if (!type) {
|
||||
type = opts.defaultType; // "string" by default
|
||||
}
|
||||
|
||||
var typedValue = f.applyTypeFunc(obj.name, obj.value, type, obj.el, typeFunctions); // Parse type as string, number, etc.
|
||||
|
||||
if (!typedValue && f.shouldSkipFalsy(obj.name, nameSansType, type, obj.el, opts)) {
|
||||
return; // ignore falsy inputs if specified in the options
|
||||
}
|
||||
|
||||
var keys = f.splitInputNameIntoKeysArray(nameSansType);
|
||||
f.deepSet(serializedObject, keys, typedValue, opts);
|
||||
});
|
||||
return serializedObject;
|
||||
};
|
||||
|
||||
// Use $.serializeJSON as namespace for the auxiliar functions
|
||||
// and to define defaults
|
||||
$.serializeJSON = {
|
||||
defaultOptions: {}, // reassign to override option defaults for all serializeJSON calls
|
||||
|
||||
defaultBaseOptions: { // do not modify, use defaultOptions instead
|
||||
checkboxUncheckedValue: undefined, // to include that value for unchecked checkboxes (instead of ignoring them)
|
||||
useIntKeysAsArrayIndex: false, // name="foo[2]" value="v" => {foo: [null, null, "v"]}, instead of {foo: ["2": "v"]}
|
||||
|
||||
skipFalsyValuesForTypes: [], // skip serialization of falsy values for listed value types
|
||||
skipFalsyValuesForFields: [], // skip serialization of falsy values for listed field names
|
||||
|
||||
disableColonTypes: false, // do not interpret ":type" suffix as a type
|
||||
customTypes: {}, // extends defaultTypes
|
||||
defaultTypes: {
|
||||
"string": function(str) { return String(str); },
|
||||
"number": function(str) { return Number(str); },
|
||||
"boolean": function(str) { var falses = ["false", "null", "undefined", "", "0"]; return falses.indexOf(str) === -1; },
|
||||
"null": function(str) { var falses = ["false", "null", "undefined", "", "0"]; return falses.indexOf(str) === -1 ? str : null; },
|
||||
"array": function(str) { return JSON.parse(str); },
|
||||
"object": function(str) { return JSON.parse(str); },
|
||||
"skip": null // skip is a special type used to ignore fields
|
||||
},
|
||||
defaultType: "string",
|
||||
},
|
||||
|
||||
// Validate and set defaults
|
||||
setupOpts: function(options) {
|
||||
if (options == null) options = {};
|
||||
var f = $.serializeJSON;
|
||||
|
||||
// Validate
|
||||
var validOpts = [
|
||||
"checkboxUncheckedValue",
|
||||
"useIntKeysAsArrayIndex",
|
||||
|
||||
"skipFalsyValuesForTypes",
|
||||
"skipFalsyValuesForFields",
|
||||
|
||||
"disableColonTypes",
|
||||
"customTypes",
|
||||
"defaultTypes",
|
||||
"defaultType"
|
||||
];
|
||||
for (var opt in options) {
|
||||
if (validOpts.indexOf(opt) === -1) {
|
||||
throw new Error("serializeJSON ERROR: invalid option '" + opt + "'. Please use one of " + validOpts.join(", "));
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to get options or defaults
|
||||
return $.extend({}, f.defaultBaseOptions, f.defaultOptions, options);
|
||||
},
|
||||
|
||||
// Just like jQuery's serializeArray method, returns an array of objects with name and value.
|
||||
// but also includes the dom element (el) and is handles unchecked checkboxes if the option or data attribute are provided.
|
||||
serializeArray: function($form, opts) {
|
||||
if (opts == null) { opts = {}; }
|
||||
var f = $.serializeJSON;
|
||||
|
||||
return $form.map(function() {
|
||||
var elements = $.prop(this, "elements"); // handle propHook "elements" to filter or add form elements
|
||||
return elements ? $.makeArray(elements) : this;
|
||||
|
||||
}).filter(function() {
|
||||
var $el = $(this);
|
||||
var type = this.type;
|
||||
|
||||
// Filter with the standard W3C rules for successful controls: http://www.w3.org/TR/html401/interact/forms.html#h-17.13.2
|
||||
return this.name && // must contain a name attribute
|
||||
!$el.is(":disabled") && // must not be disable (use .is(":disabled") so that fieldset[disabled] works)
|
||||
rsubmittable.test(this.nodeName) && !rsubmitterTypes.test(type) && // only serialize submittable fields (and not buttons)
|
||||
(this.checked || !rcheckableType.test(type) || f.getCheckboxUncheckedValue($el, opts) != null); // skip unchecked checkboxes (unless using opts)
|
||||
|
||||
}).map(function(_i, el) {
|
||||
var $el = $(this);
|
||||
var val = $el.val();
|
||||
var type = this.type; // "input", "select", "textarea", "checkbox", etc.
|
||||
|
||||
if (val == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (rcheckableType.test(type) && !this.checked) {
|
||||
val = f.getCheckboxUncheckedValue($el, opts);
|
||||
}
|
||||
|
||||
if (isArray(val)) {
|
||||
return $.map(val, function(val) {
|
||||
return { name: el.name, value: val.replace(rCRLF, "\r\n"), el: el };
|
||||
} );
|
||||
}
|
||||
|
||||
return { name: el.name, value: val.replace(rCRLF, "\r\n"), el: el };
|
||||
|
||||
}).get();
|
||||
},
|
||||
|
||||
getCheckboxUncheckedValue: function($el, opts) {
|
||||
var val = $el.attr("data-unchecked-value");
|
||||
if (val == null) {
|
||||
val = opts.checkboxUncheckedValue;
|
||||
}
|
||||
return val;
|
||||
},
|
||||
|
||||
// Parse value with type function
|
||||
applyTypeFunc: function(name, strVal, type, el, typeFunctions) {
|
||||
var typeFunc = typeFunctions[type];
|
||||
if (!typeFunc) { // quick feedback to user if there is a typo or missconfiguration
|
||||
throw new Error("serializeJSON ERROR: Invalid type " + type + " found in input name '" + name + "', please use one of " + objectKeys(typeFunctions).join(", "));
|
||||
}
|
||||
return typeFunc(strVal, el);
|
||||
},
|
||||
|
||||
// Splits a field name into the name and the type. Examples:
|
||||
// "foo" => ["foo", ""]
|
||||
// "foo:boolean" => ["foo", "boolean"]
|
||||
// "foo[bar]:null" => ["foo[bar]", "null"]
|
||||
splitType : function(name) {
|
||||
var parts = name.split(":");
|
||||
if (parts.length > 1) {
|
||||
var t = parts.pop();
|
||||
return [parts.join(":"), t];
|
||||
} else {
|
||||
return [name, ""];
|
||||
}
|
||||
},
|
||||
|
||||
// Check if this input should be skipped when it has a falsy value,
|
||||
// depending on the options to skip values by name or type, and the data-skip-falsy attribute.
|
||||
shouldSkipFalsy: function(name, nameSansType, type, el, opts) {
|
||||
var skipFromDataAttr = $(el).attr("data-skip-falsy");
|
||||
if (skipFromDataAttr != null) {
|
||||
return skipFromDataAttr !== "false"; // any value is true, except the string "false"
|
||||
}
|
||||
|
||||
var optForFields = opts.skipFalsyValuesForFields;
|
||||
if (optForFields && (optForFields.indexOf(nameSansType) !== -1 || optForFields.indexOf(name) !== -1)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var optForTypes = opts.skipFalsyValuesForTypes;
|
||||
if (optForTypes && optForTypes.indexOf(type) !== -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
// Split the input name in programatically readable keys.
|
||||
// Examples:
|
||||
// "foo" => ["foo"]
|
||||
// "[foo]" => ["foo"]
|
||||
// "foo[inn][bar]" => ["foo", "inn", "bar"]
|
||||
// "foo[inn[bar]]" => ["foo", "inn", "bar"]
|
||||
// "foo[inn][arr][0]" => ["foo", "inn", "arr", "0"]
|
||||
// "arr[][val]" => ["arr", "", "val"]
|
||||
splitInputNameIntoKeysArray: function(nameWithNoType) {
|
||||
var keys = nameWithNoType.split("["); // split string into array
|
||||
keys = $.map(keys, function (key) { return key.replace(/\]/g, ""); }); // remove closing brackets
|
||||
if (keys[0] === "") { keys.shift(); } // ensure no opening bracket ("[foo][inn]" should be same as "foo[inn]")
|
||||
return keys;
|
||||
},
|
||||
|
||||
// Set a value in an object or array, using multiple keys to set in a nested object or array.
|
||||
// This is the main function of the script, that allows serializeJSON to use nested keys.
|
||||
// Examples:
|
||||
//
|
||||
// deepSet(obj, ["foo"], v) // obj["foo"] = v
|
||||
// deepSet(obj, ["foo", "inn"], v) // obj["foo"]["inn"] = v // Create the inner obj["foo"] object, if needed
|
||||
// deepSet(obj, ["foo", "inn", "123"], v) // obj["foo"]["arr"]["123"] = v //
|
||||
//
|
||||
// deepSet(obj, ["0"], v) // obj["0"] = v
|
||||
// deepSet(arr, ["0"], v, {useIntKeysAsArrayIndex: true}) // arr[0] = v
|
||||
// deepSet(arr, [""], v) // arr.push(v)
|
||||
// deepSet(obj, ["arr", ""], v) // obj["arr"].push(v)
|
||||
//
|
||||
// arr = [];
|
||||
// deepSet(arr, ["", v] // arr => [v]
|
||||
// deepSet(arr, ["", "foo"], v) // arr => [v, {foo: v}]
|
||||
// deepSet(arr, ["", "bar"], v) // arr => [v, {foo: v, bar: v}]
|
||||
// deepSet(arr, ["", "bar"], v) // arr => [v, {foo: v, bar: v}, {bar: v}]
|
||||
//
|
||||
deepSet: function (o, keys, value, opts) {
|
||||
if (opts == null) { opts = {}; }
|
||||
var f = $.serializeJSON;
|
||||
if (isUndefined(o)) { throw new Error("ArgumentError: param 'o' expected to be an object or array, found undefined"); }
|
||||
if (!keys || keys.length === 0) { throw new Error("ArgumentError: param 'keys' expected to be an array with least one element"); }
|
||||
|
||||
var key = keys[0];
|
||||
|
||||
// Only one key, then it's not a deepSet, just assign the value in the object or add it to the array.
|
||||
if (keys.length === 1) {
|
||||
if (key === "") { // push values into an array (o must be an array)
|
||||
o.push(value);
|
||||
} else {
|
||||
o[key] = value; // keys can be object keys (strings) or array indexes (numbers)
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var nextKey = keys[1]; // nested key
|
||||
var tailKeys = keys.slice(1); // list of all other nested keys (nextKey is first)
|
||||
|
||||
if (key === "") { // push nested objects into an array (o must be an array)
|
||||
var lastIdx = o.length - 1;
|
||||
var lastVal = o[lastIdx];
|
||||
|
||||
// if the last value is an object or array, and the new key is not set yet
|
||||
if (isObject(lastVal) && isUndefined(f.deepGet(lastVal, tailKeys))) {
|
||||
key = lastIdx; // then set the new value as a new attribute of the same object
|
||||
} else {
|
||||
key = lastIdx + 1; // otherwise, add a new element in the array
|
||||
}
|
||||
}
|
||||
|
||||
if (nextKey === "") { // "" is used to push values into the nested array "array[]"
|
||||
if (isUndefined(o[key]) || !isArray(o[key])) {
|
||||
o[key] = []; // define (or override) as array to push values
|
||||
}
|
||||
} else {
|
||||
if (opts.useIntKeysAsArrayIndex && isValidArrayIndex(nextKey)) { // if 1, 2, 3 ... then use an array, where nextKey is the index
|
||||
if (isUndefined(o[key]) || !isArray(o[key])) {
|
||||
o[key] = []; // define (or override) as array, to insert values using int keys as array indexes
|
||||
}
|
||||
} else { // nextKey is going to be the nested object's attribute
|
||||
if (isUndefined(o[key]) || !isObject(o[key])) {
|
||||
o[key] = {}; // define (or override) as object, to set nested properties
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively set the inner object
|
||||
f.deepSet(o[key], tailKeys, value, opts);
|
||||
},
|
||||
|
||||
deepGet: function (o, keys) {
|
||||
var f = $.serializeJSON;
|
||||
if (isUndefined(o) || isUndefined(keys) || keys.length === 0 || (!isObject(o) && !isArray(o))) {
|
||||
return o;
|
||||
}
|
||||
var key = keys[0];
|
||||
if (key === "") { // "" means next array index (used by deepSet)
|
||||
return undefined;
|
||||
}
|
||||
if (keys.length === 1) {
|
||||
return o[key];
|
||||
}
|
||||
var tailKeys = keys.slice(1);
|
||||
return f.deepGet(o[key], tailKeys);
|
||||
}
|
||||
};
|
||||
|
||||
// polyfill Object.keys to get option keys in IE<9
|
||||
var objectKeys = function(obj) {
|
||||
if (Object.keys) {
|
||||
return Object.keys(obj);
|
||||
} else {
|
||||
var key, keys = [];
|
||||
for (key in obj) { keys.push(key); }
|
||||
return keys;
|
||||
}
|
||||
};
|
||||
|
||||
var isObject = function(obj) { return obj === Object(obj); }; // true for Objects and Arrays
|
||||
var isUndefined = function(obj) { return obj === void 0; }; // safe check for undefined values
|
||||
var isValidArrayIndex = function(val) { return /^[0-9]+$/.test(String(val)); }; // 1,2,3,4 ... are valid array indexes
|
||||
var isArray = Array.isArray || function(obj) { return Object.prototype.toString.call(obj) === "[object Array]"; };
|
||||
}));
|
||||
57
v2realbot/static/js/mychart.js
Normal file
57
v2realbot/static/js/mychart.js
Normal file
@ -0,0 +1,57 @@
|
||||
//const chartOptions = { layout: { textColor: 'black', background: { type: 'solid', color: 'white' } } };
|
||||
const chartOptions = { width: 800, height: 600}
|
||||
const chart = LightweightCharts.createChart(document.getElementById('conteiner'), chartOptions);
|
||||
chart.applyOptions({ timeScale: { visible: true, timeVisible: true, secondsVisible: true }, crosshair: {
|
||||
mode: LightweightCharts.CrosshairMode.Normal, labelVisible: true
|
||||
}})
|
||||
const candlestickSeries = chart.addCandlestickSeries();
|
||||
candlestickSeries.priceScale().applyOptions({
|
||||
scaleMargins: {
|
||||
top: 0.1, // highest point of the series will be 10% away from the top
|
||||
bottom: 0.4, // lowest point will be 40% away from the bottom
|
||||
},
|
||||
});
|
||||
const volumeSeries = chart.addHistogramSeries({title: "Volume", color: '#26a69a', priceFormat: {type: 'volume'}, priceScaleId: ''});
|
||||
volumeSeries.priceScale().applyOptions({
|
||||
// set the positioning of the volume series
|
||||
scaleMargins: {
|
||||
top: 0.7, // highest point of the series will be 70% away from the top
|
||||
bottom: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const vwapSeries = chart.addLineSeries({
|
||||
title: "vwap",
|
||||
color: '#2962FF',
|
||||
lineWidth: 1,
|
||||
})
|
||||
|
||||
|
||||
//TBD dynamicky zobrazovat vsechny indikatory
|
||||
document.getElementById('conteiner').style.display = 'inline-block';
|
||||
var cont = document.getElementById('conteiner');
|
||||
var legend = document.createElement('div');
|
||||
legend.classList.add('legend');
|
||||
cont.appendChild(legend);
|
||||
var firstRow = document.createElement('div');
|
||||
firstRow.innerText = '-';
|
||||
// firstRow.style.color = 'white';
|
||||
legend.appendChild(firstRow);
|
||||
|
||||
function pad(n) {
|
||||
var s = ('0' + n);
|
||||
return s.substr(s.length - 2);
|
||||
}
|
||||
|
||||
chart.subscribeCrosshairMove((param) => {
|
||||
if (param.time) {
|
||||
const data = param.seriesData.get(vwapSeries);
|
||||
const vwap = data.value !== undefined ? data.value : data.close;
|
||||
const bars = param.seriesData.get(candlestickSeries);
|
||||
const volumes = param.seriesData.get(volumeSeries);
|
||||
firstRow.innerText = 'VWAP' + ' ' + vwap.toFixed(2) + " O" + bars.open + " H" + bars.high + " L" + bars.low + " C" + bars.close + " V" + volumes.value;
|
||||
}
|
||||
else {
|
||||
firstRow.innerText = '-';
|
||||
}
|
||||
});
|
||||
441
v2realbot/static/js/mytables.js
Normal file
441
v2realbot/static/js/mytables.js
Normal file
@ -0,0 +1,441 @@
|
||||
|
||||
API_KEY = localStorage.getItem("api-key")
|
||||
|
||||
//on button
|
||||
function store_api_key(event) {
|
||||
key = document.getElementById("api-key").value;
|
||||
localStorage.setItem("api-key", key);
|
||||
API_KEY = key;
|
||||
}
|
||||
|
||||
function get_status(id) {
|
||||
|
||||
// for (val of stratinRecords.rows().iterator()) {
|
||||
// window.alert(JSON.stringify(val))
|
||||
// }
|
||||
var status = "stopped"
|
||||
runnerRecords.rows().iterator('row', function ( context, index ) {
|
||||
var data = this.row(index).data();
|
||||
//window.alert(JSON.stringify(data))
|
||||
if (data.id == id) {
|
||||
//window.alert("found");
|
||||
if ((data.run_mode) == "backtest") { status_detail = data.run_mode}
|
||||
else { status_detail = data.run_mode + " | " + data.run_account}
|
||||
if (data.run_paused == null) {
|
||||
status = "running | "+ status_detail
|
||||
}
|
||||
else {
|
||||
status = "paused | "+ status_detail
|
||||
}}
|
||||
//window.alert("found") }
|
||||
});
|
||||
return status
|
||||
}
|
||||
|
||||
// alert(JSON.stringify(stratinRecords.data()))
|
||||
// arr = stratinRecords.data()
|
||||
// foreach(row in arr.rows) {
|
||||
// alert(row.id)
|
||||
// }
|
||||
|
||||
// //let obj = arr.find(o => o.id2 === '2');
|
||||
// //console.log(obj);
|
||||
// //alert(JSON.stringify(obj))
|
||||
|
||||
|
||||
|
||||
|
||||
$(document).ready(function () {
|
||||
//reaload hlavni tabulky
|
||||
|
||||
stratinRecords.ajax.reload();
|
||||
runnerRecords.ajax.reload();
|
||||
|
||||
//disable buttons (enable on row selection)
|
||||
$('#button_pause').attr('disabled','disabled');
|
||||
$('#button_stop').attr('disabled','disabled');
|
||||
$('#button_add').attr('disabled','disabled');
|
||||
$('#button_edit').attr('disabled','disabled');
|
||||
$('#button_delete').attr('disabled','disabled');
|
||||
$('#button_run').attr('disabled','disabled');
|
||||
|
||||
//selectable rows in stratin table
|
||||
$('#stratinTable tbody').on('click', 'tr', function () {
|
||||
if ($(this).hasClass('selected')) {
|
||||
$(this).removeClass('selected');
|
||||
$('#button_add').attr('disabled','disabled');
|
||||
$('#button_edit').attr('disabled','disabled');
|
||||
$('#button_delete').attr('disabled','disabled');
|
||||
$('#button_run').attr('disabled','disabled');
|
||||
} else {
|
||||
stratinRecords.$('tr.selected').removeClass('selected');
|
||||
$(this).addClass('selected');
|
||||
$('#button_add').attr('disabled',false);
|
||||
$('#button_edit').attr('disabled',false);
|
||||
$('#button_delete').attr('disabled',false);
|
||||
$('#button_run').attr('disabled',false);
|
||||
}
|
||||
});
|
||||
|
||||
//selectable rows runners Table
|
||||
$('#runnerTable tbody').on('click', 'tr', function () {
|
||||
if ($(this).hasClass('selected')) {
|
||||
$(this).removeClass('selected');
|
||||
$('#button_pause').attr('disabled', 'disabled');
|
||||
$('#button_stop').attr('disabled', 'disabled');
|
||||
} else {
|
||||
stratinRecords.$('tr.selected').removeClass('selected');
|
||||
$(this).addClass('selected');
|
||||
$('#button_pause').attr('disabled', false);
|
||||
$('#button_stop').attr('disabled', false);
|
||||
}
|
||||
});
|
||||
|
||||
//button refresh
|
||||
$('#button_refresh').click(function () {
|
||||
runnerRecords.ajax.reload();
|
||||
stratinRecords.ajax.reload();
|
||||
})
|
||||
|
||||
//button pause
|
||||
$('#button_pause').click(function () {
|
||||
row = runnerRecords.row('.selected').data();
|
||||
event.preventDefault();
|
||||
$('#button_pause').attr('disabled','disabled');
|
||||
$.ajax({
|
||||
url:"/stratins/"+row.id+"/pause",
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader('X-API-Key',
|
||||
API_KEY); },
|
||||
method:"PUT",
|
||||
contentType: "application/json",
|
||||
dataType: "json",
|
||||
success:function(data){
|
||||
$('#button_pause').attr('disabled', false);
|
||||
runnerRecords.ajax.reload();
|
||||
stratinRecords.ajax.reload();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
var err = eval("(" + xhr.responseText + ")");
|
||||
window.alert(JSON.stringify(xhr));
|
||||
console.log(JSON.stringify(xhr));
|
||||
$('#button_pause').attr('disabled', false);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
//button stop
|
||||
$('#button_stop').click(function () {
|
||||
row = runnerRecords.row('.selected').data();
|
||||
event.preventDefault();
|
||||
$('#button_stop').attr('disabled','disabled');
|
||||
$.ajax({
|
||||
url:"/stratins/"+row.id+"/stop",
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader('X-API-Key',
|
||||
API_KEY); },
|
||||
method:"PUT",
|
||||
contentType: "application/json",
|
||||
dataType: "json",
|
||||
success:function(data){
|
||||
$('#button_stop').attr('disabled', false);
|
||||
setTimeout(function () {
|
||||
runnerRecords.ajax.reload();
|
||||
stratinRecords.ajax.reload();
|
||||
}, 2300)
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
var err = eval("(" + xhr.responseText + ")");
|
||||
window.alert(JSON.stringify(xhr));
|
||||
console.log(JSON.stringify(xhr));
|
||||
$('#button_stop').attr('disabled', false);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
//button stop all
|
||||
$('#button_stopall').click(function () {
|
||||
event.preventDefault();
|
||||
$('#buttonall_stop').attr('disabled','disabled');
|
||||
$.ajax({
|
||||
url:"/stratins/stop",
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader('X-API-Key',
|
||||
API_KEY); },
|
||||
method:"PUT",
|
||||
contentType: "application/json",
|
||||
dataType: "json",
|
||||
success:function(data){
|
||||
$('#button_stopall').attr('disabled', false);
|
||||
setTimeout(function () {
|
||||
runnerRecords.ajax.reload();
|
||||
stratinRecords.ajax.reload();
|
||||
}, 2300)
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
var err = eval("(" + xhr.responseText + ")");
|
||||
window.alert(JSON.stringify(xhr));
|
||||
console.log(JSON.stringify(xhr));
|
||||
$('#button_stopall').attr('disabled', false);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
//button run
|
||||
$('#button_run').click(function () {
|
||||
row = stratinRecords.row('.selected').data();
|
||||
window.$('#runModal').modal('show');
|
||||
//$('#runForm')[0].reset();
|
||||
$('#runid').val(row.id);
|
||||
});
|
||||
|
||||
//button add
|
||||
$('#button_add').click(function () {
|
||||
window.$('#recordModal').modal('show');
|
||||
$('#recordForm')[0].reset();
|
||||
$('.modal-title').html("<i class='fa fa-plus'></i> Add Record");
|
||||
$('#action').val('addRecord');
|
||||
$('#save').val('Add');
|
||||
});
|
||||
//edit button
|
||||
$('#button_edit').click(function () {
|
||||
row = stratinRecords.row('.selected').data();
|
||||
window.$('#recordModal').modal('show');
|
||||
$('#id').val(row.id);
|
||||
$('#id2').val(row.id2);
|
||||
$('#name').val(row.name);
|
||||
$('#symbol').val(row.symbol);
|
||||
$('#class_name').val(row.class_name);
|
||||
$('#script').val(row.script);
|
||||
$('#open_rush').val(row.open_rush);
|
||||
$('#close_rush').val(row.close_rush);
|
||||
$('#stratvars_conf').val(row.stratvars_conf);
|
||||
$('#add_data_conf').val(row.add_data_conf);
|
||||
$('#note').val(row.note);
|
||||
$('#history').val(row.history);
|
||||
$('.modal-title').html(" Edit Records");
|
||||
$('#action').val('updateRecord');
|
||||
$('#save').val('Save');
|
||||
});
|
||||
//delete button
|
||||
$('#button_delete').click(function () {
|
||||
row = stratinRecords.row('.selected').data();
|
||||
window.$('#delModal').modal('show');
|
||||
$('#delid').val(row.id);
|
||||
$('.modal-title').html(" Delete Record");
|
||||
$('#action').val('delRecord');
|
||||
$('#save').val('Delete');
|
||||
|
||||
});
|
||||
} );
|
||||
|
||||
//stratin table
|
||||
var stratinRecords =
|
||||
$('#stratinTable').DataTable( {
|
||||
ajax: {
|
||||
url: '/stratins/',
|
||||
dataSrc: '',
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader('X-API-Key',
|
||||
API_KEY); },
|
||||
error: function(xhr, status, error) {
|
||||
//var err = eval("(" + xhr.responseText + ")");
|
||||
//window.alert(JSON.stringify(xhr));
|
||||
console.log(JSON.stringify(xhr));
|
||||
}
|
||||
},
|
||||
columns: [{ data: 'id' },
|
||||
{data: 'id2'},
|
||||
{data: 'name'},
|
||||
{data: 'symbol'},
|
||||
{data: 'class_name'},
|
||||
{data: 'script'},
|
||||
{data: 'open_rush', visible: false},
|
||||
{data: 'close_rush', visible: false},
|
||||
{data: 'stratvars_conf', visible: false},
|
||||
{data: 'add_data_conf', visible: false},
|
||||
{data: 'note'},
|
||||
{data: 'history', visible: false},
|
||||
{data: 'id', visible: true}
|
||||
],
|
||||
columnDefs: [{
|
||||
targets: 12,
|
||||
render: function ( data, type, row ) {
|
||||
var status = get_status(data)
|
||||
return '<i class="fas fa-check-circle">'+status+'</i>'
|
||||
},
|
||||
}],
|
||||
paging: false
|
||||
} );
|
||||
|
||||
//runner table
|
||||
var runnerRecords =
|
||||
$('#runnerTable').DataTable( {
|
||||
ajax: {
|
||||
url: '/runners/',
|
||||
dataSrc: '',
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader('X-API-Key',
|
||||
API_KEY); },
|
||||
error: function(xhr, status, error) {
|
||||
//var err = eval("(" + xhr.responseText + ")");
|
||||
//window.alert(JSON.stringify(xhr));
|
||||
console.log(JSON.stringify(xhr));
|
||||
},
|
||||
// success:function(data){
|
||||
// if ( ! runnerRecords.data().count() ) {
|
||||
// $('#button_stopall').attr('disabled', 'disabled');
|
||||
// }
|
||||
// else {
|
||||
// $('#button_stopall').attr('disabled', false);
|
||||
// }
|
||||
// },
|
||||
},
|
||||
columns: [{ data: 'id' },
|
||||
{data: 'run_started'},
|
||||
{data: 'run_mode'},
|
||||
{data: 'run_account'},
|
||||
{data: 'run_paused'}
|
||||
],
|
||||
paging: false,
|
||||
processing: false
|
||||
} );
|
||||
|
||||
//modal na run
|
||||
$("#runModal").on('submit','#runForm', function(event){
|
||||
event.preventDefault();
|
||||
$('#run').attr('disabled','disabled');
|
||||
var formData = $(this).serializeJSON();
|
||||
//rename runid to id
|
||||
Object.defineProperty(formData, "id", Object.getOwnPropertyDescriptor(formData, "runid"));
|
||||
delete formData["runid"];
|
||||
if (formData.bt_from == "") {delete formData["bt_from"];}
|
||||
if (formData.bt_to == "") {delete formData["bt_to"];}
|
||||
jsonString = JSON.stringify(formData);
|
||||
//window.alert(jsonString);
|
||||
$.ajax({
|
||||
url:"/stratins/"+formData.id+"/run",
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader('X-API-Key',
|
||||
API_KEY); },
|
||||
method:"PUT",
|
||||
contentType: "application/json",
|
||||
data: jsonString,
|
||||
success:function(data){
|
||||
$('#runForm')[0].reset();
|
||||
window.$('#runModal').modal('hide');
|
||||
$('#run').attr('disabled', false);
|
||||
setTimeout(function () {
|
||||
runnerRecords.ajax.reload();
|
||||
stratinRecords.ajax.reload();
|
||||
}, 1500);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
var err = eval("(" + xhr.responseText + ")");
|
||||
window.alert(JSON.stringify(xhr));
|
||||
console.log(JSON.stringify(xhr));
|
||||
$('#run').attr('disabled', false);
|
||||
}
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
//modal na add/edit
|
||||
$("#recordModal").on('submit','#recordForm', function(event){
|
||||
if ($('#save').val() == "Add") {
|
||||
//code for add
|
||||
event.preventDefault();
|
||||
$('#save').attr('disabled','disabled');
|
||||
var formData = $(this).serializeJSON();
|
||||
jsonString = JSON.stringify(formData);
|
||||
$.ajax({
|
||||
url:"/stratins/",
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader('X-API-Key',
|
||||
API_KEY); },
|
||||
method:"POST",
|
||||
contentType: "application/json",
|
||||
dataType: "json",
|
||||
data: jsonString,
|
||||
success:function(data){
|
||||
$('#recordForm')[0].reset();
|
||||
window.$('#recordModal').modal('hide');
|
||||
$('#save').attr('disabled', false);
|
||||
setTimeout(function () {
|
||||
runnerRecords.ajax.reload();
|
||||
stratinRecords.ajax.reload();
|
||||
}, 750)
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
var err = eval("(" + xhr.responseText + ")");
|
||||
window.alert(JSON.stringify(xhr));
|
||||
console.log(JSON.stringify(xhr));
|
||||
$('#save').attr('disabled', false);
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
else {
|
||||
//code for add
|
||||
event.preventDefault();
|
||||
$('#save').attr('disabled','disabled');
|
||||
var formData = $(this).serializeJSON();
|
||||
jsonString = JSON.stringify(formData);
|
||||
$.ajax({
|
||||
url:"/stratins/"+formData.id,
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader('X-API-Key',
|
||||
API_KEY); },
|
||||
method:"PATCH",
|
||||
contentType: "application/json",
|
||||
dataType: "json",
|
||||
data: jsonString,
|
||||
success:function(data){
|
||||
$('#recordForm')[0].reset();
|
||||
window.$('#recordModal').modal('hide');
|
||||
$('#save').attr('disabled', false);
|
||||
stratinRecords.ajax.reload();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
var err = eval("(" + xhr.responseText + ")");
|
||||
window.alert(JSON.stringify(xhr));
|
||||
console.log(JSON.stringify(xhr));
|
||||
$('#save').attr('disabled', false);
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
//delete
|
||||
$("#delModal").on('submit','#delForm', function(event){
|
||||
event.preventDefault();
|
||||
$('#delete').attr('disabled','disabled');
|
||||
var formData = $(this).serializeJSON();
|
||||
$.ajax({
|
||||
url:"/stratins/"+formData.delid,
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader('X-API-Key',
|
||||
API_KEY); },
|
||||
method:"DELETE",
|
||||
contentType: "application/json",
|
||||
dataType: "json",
|
||||
success:function(data){
|
||||
$('#delForm')[0].reset();
|
||||
window.$('#delModal').modal('hide');
|
||||
$('#delete').attr('disabled', false);
|
||||
stratinRecords.ajax.reload();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
var err = eval("(" + xhr.responseText + ")");
|
||||
window.alert(JSON.stringify(xhr));
|
||||
console.log(JSON.stringify(xhr));
|
||||
$('#delete').attr('disabled', false);
|
||||
}
|
||||
|
||||
})
|
||||
});
|
||||
107
v2realbot/static/js/mywebsocket.js
Normal file
107
v2realbot/static/js/mywebsocket.js
Normal file
@ -0,0 +1,107 @@
|
||||
var ws = null;
|
||||
function connect(event) {
|
||||
var runnerId = document.getElementById("runnerId")
|
||||
var indList = []
|
||||
try {
|
||||
ws = new WebSocket("ws://localhost:8000/runners/" + runnerId.value + "/ws?api_key=" + API_KEY);
|
||||
}
|
||||
catch (err) {
|
||||
console.log("nejaky error" + err)
|
||||
}
|
||||
ws.onopen = function(event) {
|
||||
document.getElementById("status").textContent = "Connected to" + runnerId.value
|
||||
document.getElementById("bt-disc").style.display = "block"
|
||||
document.getElementById("bt-conn").style.display = "none"
|
||||
}
|
||||
ws.onmessage = function(event) {
|
||||
//var messages = document.getElementById('messages')
|
||||
//var message = document.createElement('li')
|
||||
//var content = document.createTextNode(event.data)
|
||||
//message.appendChild(content)
|
||||
//messages.appendChild(message)
|
||||
|
||||
var parsed_data = JSON.parse(event.data)
|
||||
|
||||
console.log(JSON.stringify(parsed_data))
|
||||
|
||||
//check received data and display lines
|
||||
if (parsed_data.hasOwnProperty("bars")) {
|
||||
var bar = parsed_data.bars
|
||||
candlestickSeries.update(bar);
|
||||
volumeSeries.update({
|
||||
time: bar.time,
|
||||
value: bar.volume
|
||||
});
|
||||
vwapSeries.update({
|
||||
time: bar.time,
|
||||
value: bar.vwap
|
||||
});
|
||||
}
|
||||
|
||||
if (parsed_data.hasOwnProperty("bars")) {
|
||||
var bar = parsed_data.bars
|
||||
candlestickSeries.update(bar);
|
||||
volumeSeries.update({
|
||||
time: bar.time,
|
||||
value: bar.volume
|
||||
});
|
||||
vwapSeries.update({
|
||||
time: bar.time,
|
||||
value: bar.vwap
|
||||
});
|
||||
}
|
||||
|
||||
if (parsed_data.hasOwnProperty("indicators")) {
|
||||
var indicators = parsed_data.indicators
|
||||
//if there are indicators it means there must be at least two keys (first time is there everytime)
|
||||
if (Object.keys(indicators).length > 1) {
|
||||
for (const [key, value] of Object.entries(indicators)) {
|
||||
if (key !== "time") {
|
||||
//if indicator exists in array, initialize it and store reference to array
|
||||
const searchObject= indList.find((obj) => obj.name==key);
|
||||
if (searchObject == undefined) {
|
||||
console.log("object new - init and add")
|
||||
var obj = {name: key, series: null}
|
||||
obj.series = chart.addLineSeries({
|
||||
title: key,
|
||||
lineWidth: 1,
|
||||
})
|
||||
obj.series.update({
|
||||
time: indicators.time,
|
||||
value: value});
|
||||
indList.push(obj)
|
||||
}
|
||||
//indicator exists in an array, let update it
|
||||
else {
|
||||
console.log("object found - update")
|
||||
searchObject.series.update({
|
||||
time: indicators.time,
|
||||
value: value
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`${key}: ${value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ws.onclose = function(event) {
|
||||
document.getElementById("status").textContent = "Disconnected from" + runnerId.value
|
||||
document.getElementById("bt-disc").style.display = "none"
|
||||
document.getElementById("bt-conn").style.display = "block"
|
||||
}
|
||||
event.preventDefault()
|
||||
}
|
||||
function disconnect(event) {
|
||||
ws.close()
|
||||
document.getElementById("bt-disc").style.display = "none"
|
||||
document.getElementById("bt-conn").style.display = "block"
|
||||
event.preventDefault()
|
||||
}
|
||||
function sendMessage(event) {
|
||||
var input = document.getElementById("messageText")
|
||||
ws.send(input.value)
|
||||
input.value = ''
|
||||
event.preventDefault()
|
||||
}
|
||||
86
v2realbot/strategy/StrategyOrderLimitKOKA.py
Normal file
86
v2realbot/strategy/StrategyOrderLimitKOKA.py
Normal file
@ -0,0 +1,86 @@
|
||||
from v2realbot.strategy.base import Strategy
|
||||
from v2realbot.utils.utils import parse_alpaca_timestamp, AttributeDict,trunc, zoneNY
|
||||
from v2realbot.utils.tlog import tlog, tlog_exception
|
||||
from alpaca.trading.models import TradeUpdate
|
||||
from alpaca.trading.enums import TradeEvent
|
||||
from v2realbot.enums.enums import Mode, Order, Account
|
||||
from v2realbot.indicators.indicators import ema
|
||||
from rich import print
|
||||
from random import randrange
|
||||
from alpaca.common.exceptions import APIError
|
||||
from threading import Event
|
||||
|
||||
class StrategyOrderLimitKOKA(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):
|
||||
if data.event == TradeEvent.FILL or data.event == TradeEvent.PARTIAL_FILL:
|
||||
ic("vstupujeme do orderupdatebuy")
|
||||
print(data)
|
||||
o: Order = data.order
|
||||
#dostavame zde i celkové akutální množství - ukládáme
|
||||
self.state.positions = data.position_qty
|
||||
if self.state.vars.limitka is None:
|
||||
self.state.avgp = float(data.price)
|
||||
self.state.vars.limitka = await self.interface.sell_l(price=trunc(float(o.filled_avg_price)+self.state.vars.profit,2), size=o.filled_qty)
|
||||
else:
|
||||
#avgp, pos
|
||||
self.state.avgp, self.state.positions = self.state.interface.pos()
|
||||
cena = round(float(self.state.avgp) + float(self.state.vars.profit),2)
|
||||
try:
|
||||
self.state.vars.limitka = await self.interface.repl(price=cena,orderid=self.state.vars.limitka,size=int(self.state.positions))
|
||||
except APIError as e:
|
||||
#stejne parametry - stava se pri rychle obratce, nevadi
|
||||
if e.code == 42210000: return 0,0
|
||||
else:
|
||||
print("Neslo nahradit profitku. Problem",str(e))
|
||||
raise Exception(e)
|
||||
|
||||
async def orderUpdateSell(self, data: TradeUpdate):
|
||||
if data.event == TradeEvent.PARTIAL_FILL:
|
||||
ic("partial fill udpatujeme pozice")
|
||||
self.state.avgp, self.state.positions = self.interface.pos()
|
||||
elif data.event == TradeEvent.FILL:
|
||||
#muzeme znovu nakupovat, mazeme limitku
|
||||
#self.state.blockbuy = 0
|
||||
ic("notifikace sell mazeme limitku")
|
||||
self.state.vars.limitka = None
|
||||
self.state.vars.lastbuyindex = -5
|
||||
|
||||
#this parent method is called by strategy just once before waiting for first data
|
||||
def strat_init(self):
|
||||
ic("strat INI function")
|
||||
#lets connect method overrides
|
||||
self.state.buy = self.buy
|
||||
self.state.buy_l = self.buy_l
|
||||
|
||||
#overidden methods
|
||||
def buy(self, size = None, repeat: bool = False):
|
||||
print("overriden method to size&check maximum ")
|
||||
if int(self.state.positions) >= self.state.vars.maxpozic:
|
||||
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)
|
||||
return self.state.interface.buy(size=sizer)
|
||||
|
||||
#pro experiment - nemame zde max mnozstvi
|
||||
def buy_l(self, price: float = None, size = None, repeat: bool = False):
|
||||
print("overriden buy limitka")
|
||||
if size is None: size=self.state.vars.chunk
|
||||
|
||||
#TODO pokud je cena 2 decimals, nechavam. pokud je 3 mistne delam round(x,2)
|
||||
if price is None: price=trunc(self.state.interface.get_last_price(self.symbol),2)
|
||||
ic(price)
|
||||
self.state.blockbuy = 1
|
||||
self.state.vars.lastbuyindex = self.state.bars['index'][-1]
|
||||
ic(self.state.blockbuy)
|
||||
ic(self.state.vars.lastbuyindex)
|
||||
return self.state.interface.buy_l(price=price, size=size)
|
||||
122
v2realbot/strategy/StrategyOrderLimitVykladaci.py
Normal file
122
v2realbot/strategy/StrategyOrderLimitVykladaci.py
Normal file
@ -0,0 +1,122 @@
|
||||
from v2realbot.strategy.base import Strategy
|
||||
from v2realbot.utils.utils import parse_alpaca_timestamp, ltp, AttributeDict,trunc,price2dec, zoneNY, print
|
||||
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
|
||||
#from rich import print
|
||||
from random import randrange
|
||||
from alpaca.common.exceptions import APIError
|
||||
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
|
||||
if o.status == OrderStatus.FILLED or o.status == OrderStatus.CANCELED:
|
||||
#pokud existuje objednavka v pendingbuys - vyhodime ji
|
||||
if self.state.vars.pendingbuys.pop(str(o.id), False):
|
||||
print("limit buy filled or cancelled. Vyhazujeme z pendingbuys.")
|
||||
ic(self.state.vars.pendingbuys)
|
||||
|
||||
if data.event == TradeEvent.FILL or data.event == TradeEvent.PARTIAL_FILL:
|
||||
ic("vstupujeme do orderupdatebuy")
|
||||
print(data)
|
||||
#dostavame zde i celkové akutální množství - ukládáme
|
||||
self.state.positions = data.position_qty
|
||||
if self.state.vars.limitka is None:
|
||||
self.state.avgp = float(data.price)
|
||||
price=price2dec(float(o.filled_avg_price)+self.state.vars.profit)
|
||||
self.state.vars.limitka = await self.interface.sell_l(price=price, size=o.filled_qty)
|
||||
else:
|
||||
#avgp, pos
|
||||
self.state.avgp, self.state.positions = self.state.interface.pos()
|
||||
cena = price2dec(float(self.state.avgp) + float(self.state.vars.profit))
|
||||
try:
|
||||
self.state.vars.limitka = await self.interface.repl(price=cena,orderid=self.state.vars.limitka,size=int(self.state.positions))
|
||||
except APIError as e:
|
||||
#stejne parametry - stava se pri rychle obratce, nevadi
|
||||
if e.code == 42210000: return 0,0
|
||||
else:
|
||||
print("Neslo nahradit profitku. Problem",str(e))
|
||||
raise Exception(e)
|
||||
|
||||
async def orderUpdateSell(self, data: TradeUpdate):
|
||||
if data.event == TradeEvent.PARTIAL_FILL:
|
||||
ic("partial fill jen udpatujeme pozice")
|
||||
self.state.avgp, self.state.positions = self.interface.pos()
|
||||
elif data.event == TradeEvent.FILL or data.event == TradeEvent.CANCELED:
|
||||
print("Příchozí SELL notifikace - complete FILL nebo CANCEL", data.event)
|
||||
#muzeme znovu nakupovat, mazeme limitku, blockbuy a pendingbuys
|
||||
#self.state.blockbuy = 0
|
||||
ic("notifikace sell mazeme limitku a update pozic")
|
||||
#updatujeme pozice
|
||||
self.state.avgp, self.state.positions = self.interface.pos()
|
||||
ic(self.state.avgp, self.state.positions)
|
||||
self.state.vars.limitka = None
|
||||
self.state.vars.lastbuyindex = -5
|
||||
self.state.vars.jevylozeno = 0
|
||||
await self.state.cancel_pending_buys()
|
||||
|
||||
#this parent method is called by strategy just once before waiting for first data
|
||||
def strat_init(self):
|
||||
ic("strat INI function")
|
||||
#lets connect method overrides
|
||||
self.state.buy = self.buy
|
||||
self.state.buy_l = self.buy_l
|
||||
self.state.cancel_pending_buys = self.cancel_pending_buys
|
||||
|
||||
|
||||
#overidden methods
|
||||
def buy(self, size = None, repeat: bool = False):
|
||||
print("overriden method to size&check maximum ")
|
||||
if int(self.state.positions) >= self.state.vars.maxpozic:
|
||||
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)
|
||||
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:
|
||||
print("max mnostvi naplneno")
|
||||
return 0
|
||||
if size is None: size=self.state.vars.chunk
|
||||
if price is None: price=price2dec((self.state.interface.get_last_price(self.symbol)))
|
||||
ic(price)
|
||||
print("odesilame LIMIT s cenou/qty", price, size)
|
||||
order = self.state.interface.buy_l(price=price, size=size)
|
||||
print("ukladame pendingbuys")
|
||||
self.state.vars.pendingbuys[str(order)]=price
|
||||
self.state.blockbuy = 1
|
||||
self.state.vars.lastbuyindex = self.state.bars['index'][-1]
|
||||
ic(self.state.blockbuy)
|
||||
ic(self.state.vars.lastbuyindex)
|
||||
|
||||
async def cancel_pending_buys(self):
|
||||
print("cancel pending buys called.")
|
||||
##proto v pendingbuys pridano str(), protoze UUIN nejde serializovat
|
||||
##padalo na variable changed during iteration, pridano
|
||||
if len(self.state.vars.pendingbuys)>0:
|
||||
tmp = copy.deepcopy(self.state.vars.pendingbuys)
|
||||
for key in tmp:
|
||||
ic(key)
|
||||
#nejprve vyhodime z pendingbuys
|
||||
self.state.vars.pendingbuys.pop(key, False)
|
||||
self.interface.cancel(key)
|
||||
self.state.vars.pendingbuys={}
|
||||
self.state.vars.jevylozeno = 0
|
||||
print("cancel pending buys end")
|
||||
0
v2realbot/strategy/__init__.py
Normal file
0
v2realbot/strategy/__init__.py
Normal file
Binary file not shown.
BIN
v2realbot/strategy/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
v2realbot/strategy/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
v2realbot/strategy/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
v2realbot/strategy/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
v2realbot/strategy/__pycache__/base.cpython-310.pyc
Normal file
BIN
v2realbot/strategy/__pycache__/base.cpython-310.pyc
Normal file
Binary file not shown.
BIN
v2realbot/strategy/__pycache__/base.cpython-311.pyc
Normal file
BIN
v2realbot/strategy/__pycache__/base.cpython-311.pyc
Normal file
Binary file not shown.
BIN
v2realbot/strategy/__pycache__/strategy.cpython-311.pyc
Normal file
BIN
v2realbot/strategy/__pycache__/strategy.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
449
v2realbot/strategy/base.py
Normal file
449
v2realbot/strategy/base.py
Normal file
@ -0,0 +1,449 @@
|
||||
"""
|
||||
Strategy base class
|
||||
"""
|
||||
from datetime import datetime
|
||||
from v2realbot.utils.utils import AttributeDict, zoneNY, is_open_rush, is_close_rush, json_serial
|
||||
from v2realbot.utils.tlog import tlog
|
||||
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Order, Account
|
||||
from v2realbot.config import BT_DELAYS, get_key, HEARTBEAT_TIMEOUT
|
||||
import queue
|
||||
from rich import print
|
||||
from v2realbot.loader.aggregator import TradeAggregator2Queue, TradeAggregator2List, TradeAggregator
|
||||
from v2realbot.loader.order_updates_streamer import LiveOrderUpdatesStreamer
|
||||
from v2realbot.loader.trade_offline_streamer import Trade_Offline_Streamer
|
||||
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
|
||||
from alpaca.trading.enums import OrderSide
|
||||
from v2realbot.backtesting.backtester import Backtester
|
||||
from threading import Event, current_thread
|
||||
import json
|
||||
|
||||
# obecna Parent strategie podporující queues
|
||||
class Strategy:
|
||||
def __init__(self, name: str, symbol: str, next: callable, init: callable, account: Account, mode: str = Mode.PAPER, stratvars: AttributeDict = None, open_rush: int = 30, close_rush: int = 30, pe: Event = None, se: Event = None) -> None:
|
||||
#variable to store methods overriden by strategytypes (ie pre plugins)
|
||||
self.overrides = None
|
||||
self.symbol = symbol
|
||||
self.next = next
|
||||
self.init = init
|
||||
self.mode = mode
|
||||
self.stratvars = stratvars
|
||||
self.name = name
|
||||
self.time = None
|
||||
self.rectype: RecordType = None
|
||||
self.nextnew = 1
|
||||
self.btdata: list = []
|
||||
self.interface: GeneralInterface = None
|
||||
self.state: StrategyState = None
|
||||
self.bt: Backtester = None
|
||||
self.debug = False
|
||||
#skip morning or closing rush
|
||||
self.open_rush = open_rush
|
||||
self.close_rush = close_rush
|
||||
self._streams = []
|
||||
self.account = account
|
||||
self.key = get_key(mode=self.mode, account=self.account)
|
||||
self.rtqueue = None
|
||||
|
||||
|
||||
#TODO predelat na dynamické queues
|
||||
self.q1 = queue.Queue()
|
||||
self.q2 = queue.Queue()
|
||||
self.q3 = queue.Queue()
|
||||
|
||||
self.set_mode(mode=mode)
|
||||
|
||||
#pause event and end event
|
||||
self.pe = pe
|
||||
self.se = se
|
||||
|
||||
#prdelat queue na dynamic - podle toho jak bud uchtit pracovat s multiresolutions
|
||||
#zatim jen jedna q1
|
||||
#TODO zaroven strategie musi vedet o rectypu, protoze je zpracovava
|
||||
def add_data(self,
|
||||
symbol: str,
|
||||
rectype: RecordType = RecordType.BAR,
|
||||
timeframe: int = 5,
|
||||
minsize: int = 100,
|
||||
update_ltp: bool = False,
|
||||
align: StartBarAlign = StartBarAlign.ROUND,
|
||||
mintick: int = 0,
|
||||
exthours: bool = False):
|
||||
|
||||
##TODO vytvorit self.datas_here containing dict - queue - SYMBOL - RecType -
|
||||
##zatim natvrdo
|
||||
##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._streams.append(stream)
|
||||
self.dataloader.add_stream(stream)
|
||||
|
||||
"""Allow client to set LIVE or BACKTEST mode"""
|
||||
def set_mode(self, mode: Mode, start: datetime = None, end: datetime = None, cash = None, debug: bool = False):
|
||||
ic(f"mode {mode} selected")
|
||||
|
||||
if mode == Mode.BT and (not start or not end):
|
||||
print("start/end required")
|
||||
return -1
|
||||
|
||||
self.debug = debug
|
||||
self.key = get_key(mode=mode, account=self.account)
|
||||
|
||||
if mode == Mode.LIVE or mode == Mode.PAPER:
|
||||
#data loader thread
|
||||
self.dataloader = Trade_WS_Streamer(name="WS-LDR-"+self.name)
|
||||
self.interface = LiveInterface(symbol=self.symbol, key=self.key)
|
||||
# order notif thread
|
||||
self.order_notifs = LiveOrderUpdatesStreamer(key=self.key, name="WS-STRMR-" + self.name)
|
||||
#propojujeme notifice s interfacem (pro callback)
|
||||
self.order_notifs.connect_callback(self)
|
||||
self.state = StrategyState(name=self.name, symbol = self.symbol, stratvars = self.stratvars, interface=self.interface, rectype=self.rectype)
|
||||
|
||||
elif mode == Mode.BT:
|
||||
self.dataloader = Trade_Offline_Streamer(start, end, btdata=self.btdata)
|
||||
self.bt = Backtester(symbol = self.symbol, order_fill_callback= self.order_updates, btdata=self.btdata, cash=cash, bp_from=start, bp_to=end)
|
||||
self.interface = BacktestInterface(symbol=self.symbol, bt=self.bt)
|
||||
self.state = StrategyState(name=self.name, symbol = self.symbol, stratvars = self.stratvars, interface=self.interface, rectype=self.rectype)
|
||||
self.order_notifs = None
|
||||
##streamer bude plnit trady do listu trades - nad kterym bude pracovat paper trade
|
||||
#zatim takto - pak pripadne do fajlu nebo jinak OPTIMALIZOVAT
|
||||
self.dataloader.add_stream(TradeAggregator2List(symbol=self.symbol,btdata=self.btdata,rectype=RecordType.TRADE))
|
||||
else:
|
||||
print("unknow mode")
|
||||
return -1
|
||||
|
||||
self.mode = mode
|
||||
|
||||
"""SAVE record to respective STATE variables (bar or trades)
|
||||
ukládáme i index pro případné indikátory - pro zobrazení v grafu
|
||||
----- NO support for simultaneous rectypes in one queue """
|
||||
def save_item_history(self,item):
|
||||
if self.rectype == RecordType.BAR:
|
||||
#jako cas indikatorů pridavame cas baru, jejich hodnoty se naplni v nextu
|
||||
self.state.indicators['time'].append(item['time'])
|
||||
self.append_bar(self.state.bars,item)
|
||||
elif self.rectype == RecordType.TRADE:
|
||||
pass
|
||||
#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:
|
||||
#novy vzdy pridame
|
||||
if self.nextnew:
|
||||
self.state.indicators['time'].append(item['time'])
|
||||
self.append_bar(self.state.bars,item)
|
||||
self.nextnew = 0
|
||||
#nasledujici updatneme, po potvrzeni, nasleduje novy bar
|
||||
else:
|
||||
if item['confirmed'] == 0:
|
||||
self.state.indicators['time'][-1]=item['time']
|
||||
self.replace_prev_bar(self.state.bars,item)
|
||||
#confirmed
|
||||
else:
|
||||
self.state.indicators['time'][-1]=item['time']
|
||||
self.replace_prev_bar(self.state.bars,item)
|
||||
self.nextnew = 1
|
||||
|
||||
""""refresh positions and avgp - for CBAR once per confirmed, for BARS each time"""
|
||||
def refresh_positions(self, item):
|
||||
if self.rectype == RecordType.BAR:
|
||||
self.state.avgp, self.state.positions = self.interface.pos()
|
||||
elif self.rectype == RecordType.CBAR and item['confirmed'] == 1:
|
||||
self.state.avgp, self.state.positions= self.interface.pos()
|
||||
|
||||
"""update state.last_trade_time a time of iteration"""
|
||||
def update_times(self, item):
|
||||
if self.rectype == RecordType.BAR or self.rectype == RecordType.CBAR:
|
||||
self.state.last_trade_time = item['updated']
|
||||
elif self.rectype == RecordType.TRADE:
|
||||
self.state.last_trade_time = item['t']
|
||||
if self.mode == Mode.BT:
|
||||
self.bt.time = self.state.last_trade_time + BT_DELAYS.trigger_to_strat
|
||||
self.state.time = self.state.last_trade_time + BT_DELAYS.trigger_to_strat
|
||||
elif self.mode == Mode.LIVE or self.mode == Mode.PAPER:
|
||||
self.state.time = datetime.now().timestamp()
|
||||
ic('time updated')
|
||||
def strat_loop(self, item):
|
||||
|
||||
if self.debug:
|
||||
a = input("Press key before next iteration")
|
||||
|
||||
self.update_times(item)
|
||||
## BT - execute orders that should have been filled until this time
|
||||
##do objektu backtest controller?
|
||||
|
||||
ic(self.state.time)
|
||||
|
||||
if self.mode == Mode.BT:
|
||||
ic(self.bt.time)
|
||||
#pozor backtester muze volat order_updates na minuly cas - nastavi si bt.time
|
||||
self.bt.execute_orders_and_callbacks(self.state.time)
|
||||
ic(self.bt.time)
|
||||
|
||||
ic(self.state.time)
|
||||
|
||||
#volame jeste jednou update_times, kdyby si BT nastavil interfaces na jiny cas (v ramci callbacku notifikací)
|
||||
self.update_times(item)
|
||||
ic(self.state.time)
|
||||
|
||||
if self.mode == Mode.BT:
|
||||
ic(self.bt.time)
|
||||
ic(len(self.btdata))
|
||||
ic(self.bt.cash)
|
||||
|
||||
self.save_item_history(item)
|
||||
#nevyhodit ten refresh do TypeLimit? asi ANO
|
||||
self.refresh_positions(item)
|
||||
#calling plugin (can be overriden to do some additional steps)
|
||||
self.before_iteration()
|
||||
ted = datetime.fromtimestamp(self.state.time).astimezone(zoneNY)
|
||||
if is_open_rush(ted, self.open_rush) or is_close_rush(ted, self.close_rush):
|
||||
print("Rush hour - skipping")
|
||||
else:
|
||||
self.next(item, self.state)
|
||||
self.after_iteration(item)
|
||||
|
||||
##run strategy live
|
||||
def start(self):
|
||||
|
||||
if not self.dataloader:
|
||||
print("Set mode first")
|
||||
|
||||
print(40*"-",self.mode, "STRATEGY ***", self.name,"*** STARTING",40*"-")
|
||||
#data loader thread
|
||||
self.dataloader.start()
|
||||
|
||||
if self.mode == Mode.LIVE or self.mode == Mode.PAPER:
|
||||
#live notification thread
|
||||
self.order_notifs.start()
|
||||
else:
|
||||
self.bt.backtest_start = datetime.now()
|
||||
|
||||
self.strat_init()
|
||||
#print(self.init)
|
||||
self.init(self.state)
|
||||
|
||||
#main strat loop
|
||||
print(self.name, "Waiting for DATA")
|
||||
while True:
|
||||
try:
|
||||
#block 5s, after that check signals
|
||||
item = self.q1.get(timeout=HEARTBEAT_TIMEOUT)
|
||||
except queue.Empty:
|
||||
#check signals
|
||||
if self.se.is_set():
|
||||
print(current_thread().name, "Stopping signal")
|
||||
break
|
||||
if self.pe.is_set():
|
||||
print(current_thread().name, "Paused.")
|
||||
continue
|
||||
else:
|
||||
print(current_thread().name, "HEARTBEAT - no trades or signals")
|
||||
continue
|
||||
#prijde posledni zaznam nebo stop event signal
|
||||
if item == "last" or self.se.is_set():
|
||||
print(current_thread().name, "stopping")
|
||||
break
|
||||
elif self.pe.is_set():
|
||||
print(current_thread().name, "Paused.")
|
||||
continue
|
||||
print("New data ingested")
|
||||
#calling main loop
|
||||
self.strat_loop(item=item)
|
||||
|
||||
tlog(f"FINISHED")
|
||||
print(40*"*",self.mode, "STRATEGY ", self.name,"STOPPING",40*"*")
|
||||
|
||||
self.stop()
|
||||
|
||||
if self.mode == Mode.BT:
|
||||
self.bt.backtest_end = datetime.now()
|
||||
print(40*"*",self.mode, "BACKTEST RESULTS",40*"*")
|
||||
#-> account, cash,trades,open_orders
|
||||
self.bt.display_backtest_result(self.state)
|
||||
|
||||
#this is(WILL BE) called when strategy is stopped
|
||||
# LIVE - pause or stop signal received
|
||||
# BT - last item processed signal received
|
||||
def stop(self):
|
||||
|
||||
#disconnect strategy from websocket trader updates
|
||||
if self.mode == Mode.LIVE or self.mode == Mode.PAPER:
|
||||
self.order_notifs.disconnect_callback(self)
|
||||
|
||||
#necessary only for shared loaders (to keep it running for other stratefies)
|
||||
for i in self._streams:
|
||||
print(self.name, "Removing stream",i)
|
||||
self.dataloader.remove_stream(i)
|
||||
#pamatujeme si streamy, ktere ma strategie a tady je removneme
|
||||
|
||||
#zavolame na loaderu remove streamer - mohou byt dalsi bezici strategie, ktery loader vyuzivaji
|
||||
#pripadne udelat shared loader a nebo dedicated loader
|
||||
#pokud je shared tak volat remove
|
||||
|
||||
#refactor for multiprocessing
|
||||
# if self.mode == Mode.LIVE:
|
||||
# self.order_notifs.stop()
|
||||
# self.dataloader.stop()
|
||||
|
||||
#for order updates from LIVE or BACKTEST
|
||||
#updates are sent only for SYMBOL of strategy
|
||||
|
||||
async def order_updates(self, data):
|
||||
if self.mode == Mode.LIVE or self.mode == Mode.PAPER:
|
||||
now = datetime.now().timestamp()
|
||||
else:
|
||||
now = self.bt.time
|
||||
|
||||
print("NOTIFICATION ARRIVED AT:", now)
|
||||
|
||||
##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)
|
||||
|
||||
async def orderUpdateBuy(self, data):
|
||||
print(data)
|
||||
|
||||
async def orderUpdateSell(self,data):
|
||||
print(data)
|
||||
|
||||
##method to override by child class. Allows to call specific code right before running next iteration.
|
||||
def before_iteration(self):
|
||||
pass
|
||||
|
||||
##kroky po iteraci
|
||||
def after_iteration(self, item):
|
||||
|
||||
##check if real time chart is requested
|
||||
##posilame dict s objekty: bars, trades podle cbaru, a dale indicators naplnene time a pripadnymi identifikatory (EMA)
|
||||
if self.rtqueue is not None:
|
||||
rt_out = dict()
|
||||
|
||||
if self.rectype == RecordType.BAR or self.rectype == RecordType.CBAR:
|
||||
rt_out["bars"] = item
|
||||
else:
|
||||
rt_out["trades"] = item
|
||||
#get only last values from indicators, if there are any indicators present
|
||||
if len(self.state.indicators) > 0:
|
||||
rt_out["indicators"] = dict()
|
||||
for key, value in self.state.indicators.items():
|
||||
rt_out["indicators"][key] = value[-1]
|
||||
print(rt_out)
|
||||
|
||||
print("RTQUEUE INSERT")
|
||||
#send current values to Realtime display on frontend
|
||||
#all datetime values are converted to timestamp
|
||||
self.rtqueue.put(json.dumps(rt_out, default=json_serial))
|
||||
print("RTQUEUE", self.rtqueue)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# inicializace poplatna typu strategie (např. u LIMITu dotažení existující limitky)
|
||||
def strat_init(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def append_bar(history_reference, new_bar: dict):
|
||||
history_reference['open'].append(new_bar['open'])
|
||||
history_reference['high'].append(new_bar['high'])
|
||||
history_reference['low'].append(new_bar['low'])
|
||||
history_reference['close'].append(new_bar['close'])
|
||||
history_reference['hlcc4'].append(new_bar['hlcc4'])
|
||||
history_reference['volume'].append(new_bar['volume'])
|
||||
history_reference['time'].append(new_bar['time'])
|
||||
history_reference['trades'].append(new_bar['trades'])
|
||||
history_reference['resolution'].append(new_bar['resolution'])
|
||||
history_reference['vwap'].append(new_bar['vwap'])
|
||||
history_reference['confirmed'].append(new_bar['confirmed'])
|
||||
history_reference['index'].append(new_bar['index'])
|
||||
history_reference['updated'].append(new_bar['updated'])
|
||||
|
||||
@staticmethod
|
||||
def append_trade(history_reference, new_trade: dict):
|
||||
history_reference['t'].append(new_trade['t'])
|
||||
history_reference['x'].append(new_trade['x'])
|
||||
history_reference['p'].append(new_trade['p'])
|
||||
history_reference['s'].append(new_trade['s'])
|
||||
history_reference['c'].append(new_trade['c'])
|
||||
history_reference['i'].append(new_trade['i'])
|
||||
history_reference['z'].append(new_trade['z'])
|
||||
|
||||
@staticmethod
|
||||
def replace_prev_bar(history_reference, new_bar: dict):
|
||||
history_reference['open'][-1]=new_bar['open']
|
||||
history_reference['high'][-1]=new_bar['high']
|
||||
history_reference['low'][-1]=new_bar['low']
|
||||
history_reference['close'][-1]=new_bar['close']
|
||||
history_reference['hlcc4'][-1]=new_bar['hlcc4']
|
||||
history_reference['volume'][-1]=new_bar['volume']
|
||||
history_reference['time'][-1]=new_bar['time']
|
||||
history_reference['trades'][-1]=new_bar['trades']
|
||||
history_reference['resolution'][-1]=new_bar['resolution']
|
||||
history_reference['vwap'][-1]=new_bar['vwap']
|
||||
history_reference['confirmed'][-1]=new_bar['confirmed']
|
||||
history_reference['index'][-1]=new_bar['index']
|
||||
history_reference['updated'][-1]=new_bar['updated']
|
||||
class StrategyState:
|
||||
"""Strategy Stat object that is passed to callbacks
|
||||
note:
|
||||
state.time
|
||||
state.interface.time
|
||||
většinou mají stejnou hodnotu, ale lišit se mužou např. v případě BT callbacku - kdy se v rámci okna končící state.time realizují objednávky, které
|
||||
triggerují callback, který následně vyvolá např. buy (ten se musí ale udít v čase fillu, tzn. callback si nastaví čas interfacu na filltime)
|
||||
po dokončení bt kroků před zahájením iterace "NEXT" se časy znovu updatnout na původni state.time
|
||||
"""
|
||||
def __init__(self, name: str, symbol: str, stratvars: AttributeDict, bars: AttributeDict = {}, trades: AttributeDict = {}, interface: GeneralInterface = None, rectype: RecordType = RecordType.BAR):
|
||||
self.vars = stratvars
|
||||
self.interface = interface
|
||||
self.positions = 0
|
||||
self.avgp = 0
|
||||
self.blockbuy = 0
|
||||
self.name = name
|
||||
self.symbol = symbol
|
||||
self.rectype = rectype
|
||||
#LIVE - now()
|
||||
#BACKTEST - allows the interface to realize past events
|
||||
self.time = 0
|
||||
#time of last trade processed
|
||||
self.last_trade_time = 0
|
||||
self.timeframe = None
|
||||
|
||||
bars = {'high': [],
|
||||
'low': [],
|
||||
'volume': [],
|
||||
'close': [],
|
||||
'hlcc4': [],
|
||||
'open': [],
|
||||
'time': [],
|
||||
'trades':[],
|
||||
'resolution':[],
|
||||
'confirmed': [],
|
||||
'vwap': [],
|
||||
'updated': [],
|
||||
'index': []}
|
||||
|
||||
trades = {'t': [],
|
||||
'x': [],
|
||||
'p': [],
|
||||
's': [],
|
||||
'c': [],
|
||||
'i': [],
|
||||
'z': []}
|
||||
|
||||
self.bars = AttributeDict(bars)
|
||||
self.trades = AttributeDict(trades)
|
||||
self.indicators = AttributeDict(time=[])
|
||||
#these methods can be overrided by StrategyType (to add or alter its functionality)
|
||||
self.buy = self.interface.buy
|
||||
self.buy_l = self.interface.buy_l
|
||||
self.sell = self.interface.sell
|
||||
self.sell_l = self.interface.sell_l
|
||||
self.cancel_pending_buys = None
|
||||
99
v2realbot/strategy/strategyOrderLimit.py
Normal file
99
v2realbot/strategy/strategyOrderLimit.py
Normal file
@ -0,0 +1,99 @@
|
||||
from strategy.base import Strategy
|
||||
from utils.utils import zoneNY
|
||||
from datetime import datetime, timedelta
|
||||
from utils.utils import parse_alpaca_timestamp, ltp, AttributeDict,trunc
|
||||
from utils.tlog import tlog, tlog_exception
|
||||
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Order, Account
|
||||
from alpaca.data.enums import DataFeed
|
||||
from alpaca.trading.models import TradeUpdate
|
||||
from alpaca.trading.enums import TradeEvent
|
||||
import queue
|
||||
from indicators import ema
|
||||
from rich import print
|
||||
from loader.aggregator import TradeAggregator2Queue, TradeAggregator2List
|
||||
from loader.order_updates_streamer import LiveOrderUpdatesStreamer
|
||||
from loader.trade_offline_streamer import Trade_Offline_Streamer
|
||||
from loader.trade_ws_streamer import Trade_WS_Streamer
|
||||
from random import randrange
|
||||
from queue import Queue
|
||||
from alpaca.trading.stream import TradingStream
|
||||
from interfaces.general_interface import GeneralInterface
|
||||
from interfaces.backtest_interface import BacktestInterface
|
||||
from interfaces.live_interface import LiveInterface
|
||||
from alpaca.trading.enums import OrderSide, QueryOrderStatus
|
||||
from alpaca.common.exceptions import APIError
|
||||
from backtesting.backtester import Backtester
|
||||
|
||||
|
||||
class StrategyOrderLimit(Strategy):
|
||||
def __init__(self, name: str, symbol: str, next: callable, init: callable, account: Account, mode: Mode = Mode.PAPER, stratvars: AttributeDict = None, debug: bool = False) -> None:
|
||||
super().__init__(name, symbol, next, init, account, mode, stratvars, debug)
|
||||
|
||||
async def orderUpdateBuy(self, data: TradeUpdate):
|
||||
if data.event == TradeEvent.FILL or data.event == TradeEvent.PARTIAL_FILL:
|
||||
ic("vstupujeme do orderupdatebuy")
|
||||
print(data)
|
||||
o: Order = data.order
|
||||
#dostavame zde i celkové akutální množství - ukládáme
|
||||
self.state.positions = data.position_qty
|
||||
if self.state.vars.limitka is None:
|
||||
self.state.avgp = float(data.price)
|
||||
self.state.vars.limitka = await self.interface.sell_l(price=trunc(float(o.filled_avg_price)+self.state.vars.profit,2), size=o.filled_qty)
|
||||
else:
|
||||
#avgp, pos
|
||||
self.state.avgp, self.state.positions = self.state.interface.pos()
|
||||
cena = round(float(self.state.avgp) + float(self.state.vars.profit),2)
|
||||
try:
|
||||
self.state.vars.limitka = await self.interface.repl(price=cena,orderid=self.state.vars.limitka,size=int(self.state.positions))
|
||||
except APIError as e:
|
||||
#stejne parametry - stava se pri rychle obratce, nevadi
|
||||
if e.code == 42210000: return 0,0
|
||||
else:
|
||||
print("Neslo nahradit profitku. Problem",str(e))
|
||||
raise Exception(e)
|
||||
|
||||
async def orderUpdateSell(self, data: TradeUpdate):
|
||||
if data.event == TradeEvent.PARTIAL_FILL:
|
||||
ic("partial fill udpatujeme pozice")
|
||||
self.state.avgp, self.state.positions = self.interface.pos()
|
||||
elif data.event == TradeEvent.FILL:
|
||||
#muzeme znovu nakupovat, mazeme limitku
|
||||
#self.state.blockbuy = 0
|
||||
ic("notifikace sell mazeme limitku")
|
||||
self.state.vars.limitka = None
|
||||
self.state.vars.lastbuyindex = -5
|
||||
|
||||
#this parent method is called by strategy just once before waiting for first data
|
||||
def strat_init(self):
|
||||
ic("strat INI function")
|
||||
#lets connect method overrides
|
||||
self.state.buy = self.buy
|
||||
self.state.buy_l = self.buy_l
|
||||
|
||||
#overidden methods
|
||||
def buy(self, size = None, repeat: bool = False):
|
||||
print("overriden method to size&check maximum ")
|
||||
if int(self.state.positions) >= self.state.vars.maxpozic:
|
||||
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)
|
||||
return self.state.interface.buy(size=sizer)
|
||||
|
||||
#pro experiment - nemame zde max mnozstvi
|
||||
def buy_l(self, price: float = None, size = None, repeat: bool = False):
|
||||
print("overriden buy limitka")
|
||||
if size is None: size=self.state.vars.chunk
|
||||
if price is None: price=trunc(self.state.interface.get_last_price(self.symbol)-0.01,2)
|
||||
ic(price)
|
||||
self.state.blockbuy = 1
|
||||
self.state.vars.lastbuyindex = self.state.bars['index'][-1]
|
||||
ic(self.state.blockbuy)
|
||||
ic(self.state.vars.lastbuyindex)
|
||||
return self.state.interface.buy_l(price=price, size=size)
|
||||
68
v2realbot/strategy/strategyOrderLimitWatched.py
Normal file
68
v2realbot/strategy/strategyOrderLimitWatched.py
Normal file
@ -0,0 +1,68 @@
|
||||
from strategy.base import Strategy
|
||||
from utils.utils import zoneNY
|
||||
from datetime import datetime, timedelta
|
||||
from utils.utils import parse_alpaca_timestamp, ltp, AttributeDict,trunc
|
||||
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Order, Account
|
||||
from alpaca.trading.models import TradeUpdate
|
||||
from alpaca.trading.enums import TradeEvent
|
||||
from rich import print
|
||||
|
||||
|
||||
|
||||
class StrategyOrderLimitWatched(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) -> None:
|
||||
super().__init__(name, symbol, next, init, account, mode, stratvars, open_rush, close_rush)
|
||||
|
||||
async def orderUpdateBuy(self, data: TradeUpdate):
|
||||
if data.event == TradeEvent.FILL:
|
||||
ic("orderbuyfill callback")
|
||||
print(data)
|
||||
o: Order = data.order
|
||||
#dostavame zde i celkové akutální množství - ukládáme
|
||||
self.state.positions = data.position_qty
|
||||
self.state.avgp = float(data.price)
|
||||
self.state.vars.watched = self.state.avgp
|
||||
self.state.vars.wait = False
|
||||
|
||||
async def orderUpdateSell(self, data: TradeUpdate):
|
||||
if data.event == TradeEvent.FILL:
|
||||
print(data)
|
||||
self.state.positions = data.position_qty
|
||||
self.state.vars.watched = None
|
||||
self.state.vars.wait = False
|
||||
ic("SELL notifikace callback - prodano - muzeme znovu nakupovat")
|
||||
|
||||
#this parent method is called by strategy just once before waiting for first data
|
||||
def strat_init(self):
|
||||
ic("strat INI function")
|
||||
#lets connect method overrides
|
||||
self.state.buy = self.buy
|
||||
self.state.buy_l = self.buy_l
|
||||
|
||||
#overidden methods
|
||||
def buy(self, size = None, repeat: bool = False):
|
||||
print("overriden method to size&check maximum ")
|
||||
# if int(self.state.positions) >= self.state.vars.maxpozic:
|
||||
# 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)
|
||||
return self.state.interface.buy(size=sizer)
|
||||
|
||||
#pro experiment - nemame zde max mnozstvi
|
||||
def buy_l(self, price: float = None, size = None, repeat: bool = False):
|
||||
print("overriden buy limitka")
|
||||
if size is None: size=self.state.vars.chunk
|
||||
if price is None: price=trunc(self.state.interface.get_last_price(self.symbol)-0.01,2)
|
||||
ic(price)
|
||||
self.state.blockbuy = 1
|
||||
self.state.vars.lastbuyindex = self.state.bars['index'][-1]
|
||||
ic(self.state.blockbuy)
|
||||
ic(self.state.vars.lastbuyindex)
|
||||
return self.state.interface.buy_l(price=price, size=size)
|
||||
163
v2realbot/strategy/strategyType_Limit.py
Normal file
163
v2realbot/strategy/strategyType_Limit.py
Normal file
@ -0,0 +1,163 @@
|
||||
from enums import RecordType, StartBarAlign, Mode, Order
|
||||
from datetime import datetime, timedelta
|
||||
from utils import parse_alpaca_timestamp, ltp, AttributeDict
|
||||
|
||||
from alpaca.data.enums import DataFeed
|
||||
import queue
|
||||
from indicators import ema
|
||||
from rich import print
|
||||
from random import randrange
|
||||
from queue import Queue
|
||||
from alpaca.trading.stream import TradingStream
|
||||
from alpaca.trading.enums import OrderSide, OrderStatus
|
||||
from strategy.base import Strategy, StrategyState
|
||||
"""
|
||||
StrategyTypeLimit - Třída pro specifický typ strategie.
|
||||
Např. práce s BARy(ukládá historii) a obsahuje specifický handling
|
||||
notifikací (vytváření prodejní limitky)
|
||||
|
||||
rectype support: BAR, CBAR (stores history)
|
||||
|
||||
obsahuje specifický handling pro automatické vytváření limitky
|
||||
|
||||
obsahuje rozšířené buy, sell funkce (předsunuté před interface)
|
||||
"""
|
||||
class StrategyTypeLimit(Strategy):
|
||||
def __init__(self, symbol: str, paper, next: callable, init: callable, stratvars: dict = None) -> None:
|
||||
super().__init__(symbol, paper, next, init, stratvars)
|
||||
bars = {'high': [],
|
||||
'low': [],
|
||||
'volume': [],
|
||||
'close': [],
|
||||
'hlcc4': [],
|
||||
'open': [],
|
||||
'time': [],
|
||||
'trades':[],
|
||||
'resolution':[],
|
||||
'confirmed': [],
|
||||
'vwap': [],
|
||||
'index': []}
|
||||
|
||||
self.state = StrategyState(variables=stratvars,bars=bars,oe=self.interface)
|
||||
self.first = 1
|
||||
self.looprectype = RecordType.BAR
|
||||
self.nextnew = 1
|
||||
|
||||
## overriden strategy loop function to store history
|
||||
# logika je jina pro BAR a CBAR - dopracovat dynamickou podporu streamtypu + queues
|
||||
# zatim odliseno takto: prvni bar -unconfirmed=CBAR -confirmed=BAR
|
||||
|
||||
|
||||
##TODO pridat automatické plnení state.positions a state.avgp
|
||||
|
||||
def strat_loop(self):
|
||||
#do budoucna rozlisit mezi typy event - trade nebo bar
|
||||
bar = self.q1.get()
|
||||
|
||||
#podle prvni iterace pripadne overridnem looprectype
|
||||
if self.first==1 and bar['confirmed'] == 0:
|
||||
self.looprectype = RecordType.CBAR
|
||||
print("jde o CBARs")
|
||||
self.first = 0
|
||||
|
||||
if self.looprectype == RecordType.BAR:
|
||||
self.append_bar(self.state.bars,bar)
|
||||
|
||||
elif self.looprectype == RecordType.CBAR:
|
||||
#novy vzdy pridame
|
||||
if self.nextnew:
|
||||
self.append_bar(self.state.bars,bar)
|
||||
self.nextnew = 0
|
||||
#nasledujici updatneme, po potvrzeni, nasleduje nvoy bar
|
||||
else:
|
||||
if bar['confirmed'] == 0:
|
||||
self.replace_prev_bar(self.state.bars,bar)
|
||||
#confirmed
|
||||
else:
|
||||
self.replace_prev_bar(self.state.bars,bar)
|
||||
self.nextnew = 1
|
||||
|
||||
self.next(bar, self.state)
|
||||
|
||||
#předsazená order logika relevantní pro strategii
|
||||
def buy(self, size: int = 1, repeat: bool = False):
|
||||
# some code before
|
||||
self.interface.buy(size = size, repeat = repeat)
|
||||
# some code after
|
||||
|
||||
#specifika pro notif callback
|
||||
async def orderUpdateSell(self, data):
|
||||
|
||||
##SELL and FILLED or CANCELLED
|
||||
order: Order = data.order
|
||||
if order.status == OrderStatus.FILLED or order.status == OrderStatus.CANCELED:
|
||||
print("NOTIF: incoming SELL:", order.status)
|
||||
#reset pos a avg
|
||||
self.avgp, self.poz = self.pos()
|
||||
self.limitka = 0
|
||||
|
||||
#specifika pro notif callback
|
||||
async def orderUpdateBuy(self, data):
|
||||
order: Order = data.order
|
||||
if order.side == OrderSide.BUY and (order.status == OrderStatus.FILLED or order.status == OrderStatus.PARTIALLY_FILLED):
|
||||
print("NOTIF: incoming BUY and FILLED")
|
||||
#ukladame posledni nakupni cenu do lastbuy
|
||||
self.lastbuy = order.filled_avg_price
|
||||
|
||||
#existuje limitka delam replace
|
||||
if self.limitka != 0:
|
||||
self.avgp, self.poz = self.pos()
|
||||
cena: float = 0
|
||||
#TUNING - kdyz z nejakeho duvodu je poz = 0 (predbehnuto v rush hour) tak limitka je taky, zrusena tzn. rusime si ji i u sebe
|
||||
if int(self.poz) == 0:
|
||||
print("synchro - pozice byla 0 - nastavujeme limitku na 0")
|
||||
self.limitka = 0
|
||||
return 0,0
|
||||
else:
|
||||
#OPT tady vyladit
|
||||
cena = round(float(self.avgp) + float(self.stratvars["profit"]),2)
|
||||
print("limitka existuje nahrazujeme - avg cena",self.avgp,"nastavujeme cenu",cena)
|
||||
try:
|
||||
self.limitka = await self.repl(cena,self.limitka,self.poz)
|
||||
|
||||
except APIError as e:
|
||||
#stejne parametry - stava se pri rychle obratce, nevadi
|
||||
if e.code == 42210000: return 0,0
|
||||
else:
|
||||
print("Neslo nahradit profitku. Problem",str(e))
|
||||
raise Exception(e)
|
||||
|
||||
#limitka neni
|
||||
else:
|
||||
|
||||
# vyjimka, kdyz limitka neni, ale je vic objednavek (napriklad po crashi) - vytvorime limitku na celkove mnozstvi
|
||||
|
||||
#TODO toto jen docasne nejspis pryc
|
||||
#OPTIMALIZOVAT - aby pred limitkou se nemusela volat, mozna dat cely tento blok pryc
|
||||
#a nechat vytvorit limitku pouze pro domnelou prvni a pak nasledujici to vsechno srovnat
|
||||
self.avgp, self.poz = self.pos()
|
||||
|
||||
#mohlo se stat, ze se uz do prichodu notifikace prodala
|
||||
if int(self.poz) == 0: return
|
||||
|
||||
print("selfpoz kdyz limitka neni", self.poz)
|
||||
if int(self.poz) > int(order.qty):
|
||||
|
||||
avgcena = self.avgp
|
||||
mnozstvi = self.poz
|
||||
else:
|
||||
avgcena = float(order.filled_avg_price)
|
||||
mnozstvi = order.filled_qty
|
||||
#nevolame pos, melo by byt 0, bereme avg_price z orderu
|
||||
#pokud bude avg_price 32.236
|
||||
#OPT
|
||||
cena = round(float(avgcena) + float(self.stratvars["profit"]),2)
|
||||
print("limitka neni vytvarime, a to za cenu",cena,"mnozstvi",mnozstvi)
|
||||
print("aktuzalni ltp",ltp.price[self.symbol])
|
||||
|
||||
try:
|
||||
self.limitka = await self.sell(cena, mnozstvi)
|
||||
print("vytvorena limitka", self.limitka)
|
||||
except Exception as e:
|
||||
print("Neslo vytvorit profitku. Problem",str(e))
|
||||
raise Exception(e)
|
||||
0
v2realbot/utils/__init__.py
Normal file
0
v2realbot/utils/__init__.py
Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user