first commit

This commit is contained in:
David Brazda
2023-04-12 21:00:03 +02:00
commit af9e944928
158 changed files with 19422 additions and 0 deletions

View 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()

View 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()

View 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()

View 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()

View 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()

View 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.

View 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
View File

Binary file not shown.

Binary file not shown.

View File

View 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?")

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

100
v2realbot/common/model.py Normal file
View 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
View 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
View 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

View File

View 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")

View 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()

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

47
v2realbot/enums/enums.py Normal file
View 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"

View File

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

View 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

View File

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

View 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

View 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]

View File

Binary file not shown.

Binary file not shown.

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

View 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()

View 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*"*")

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

View 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}

View 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="&#x2026;";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});

View 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(/&nbsp;/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">&#x2026;</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,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;"):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});

View 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]"; };
}));

View 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 = '-';
}
});

View 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);
}
})
});

View 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()
}

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

View 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")

View File

Binary file not shown.

Binary file not shown.

449
v2realbot/strategy/base.py Normal file
View 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

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

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

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

View File

Some files were not shown because too many files have changed in this diff Show More