Unaweza kusoma kuhusu msingi wa KDB+, lugha ya programu ya Q ni nini, uwezo wao na udhaifu wao ni katika uliopita.
Utangulizi
KDB+ ni hifadhidata ya safu iliyozingatia idadi kubwa sana ya data, iliyopangwa kwa njia maalum (kimsingi kwa wakati). Inatumika hasa katika taasisi za fedha - benki, fedha za uwekezaji, makampuni ya bima. Lugha ya Q ni lugha ya ndani ya KDB+ inayokuruhusu kufanya kazi na data hii kwa ufanisi. Itikadi ya Q ni ufupi na ufanisi, wakati uwazi unatolewa. Hii inathibitishwa na ukweli kwamba lugha ya vector itakuwa vigumu kuelewa kwa hali yoyote, na ufupi na utajiri wa kurekodi utapata kuona sehemu kubwa zaidi ya programu kwenye skrini moja, ambayo hatimaye inafanya iwe rahisi kuelewa.
Katika makala haya tunatekeleza programu kamili katika Q na unaweza kutaka kuijaribu. Ili kufanya hivyo, utahitaji Q halisi. Unaweza kupakua toleo la bure la 32-bit kwenye tovuti ya kampuni ya kx -
Taarifa ya tatizo
Kuna chanzo ambacho hutuma jedwali na data kila baada ya milisekunde 25. Kwa kuwa KDB+ inatumiwa hasa katika masuala ya fedha, tutafikiri kwamba hii ni jedwali la shughuli (biashara), ambalo lina safu wima zifuatazo: wakati (wakati katika milisekunde), sym (jina la kampuni kwenye soko la hisa - IBM, AAPL,β¦), bei (bei ambayo hisa zilinunuliwa), saizi (ukubwa wa muamala). Muda wa millisecond 25 ni wa kiholela, sio mdogo sana na sio mrefu sana. Uwepo wake unamaanisha kuwa data inakuja kwa huduma ambayo tayari imehifadhiwa. Itakuwa rahisi kutekeleza uakibishaji kwenye upande wa huduma, ikijumuisha kuakibisha kwa nguvu kulingana na mzigo wa sasa, lakini kwa unyenyekevu, tutazingatia muda uliowekwa.
Huduma lazima ihesabu kila dakika kwa kila ishara inayoingia kutoka kwa safu wima seti ya vitendaji vya kujumlisha - bei ya juu, bei ya wastani, saizi ya jumla, n.k. habari muhimu. Kwa unyenyekevu, tutafikiri kwamba kazi zote zinaweza kuhesabiwa kwa kuongezeka, i.e. ili kupata thamani mpya, inatosha kujua nambari mbili - za zamani na zinazoingia. Kwa mfano, nambari za kukokotoa za max, wastani, jumla zina mali hii, lakini kitendakazi cha wastani hakina.
Pia tutachukulia kuwa mtiririko wa data unaoingia umepangwa kwa wakati. Hii itatupa fursa ya kufanya kazi tu na dakika ya mwisho. Kwa mazoezi, inatosha kuweza kufanya kazi na dakika za sasa na zilizopita ikiwa sasisho zingine zimechelewa. Kwa unyenyekevu, hatutazingatia kesi hii.
Vipengele vya kujumlisha
Vipengele vinavyohitajika vya kujumlisha vimeorodheshwa hapa chini. Nilichukua wengi wao iwezekanavyo ili kuongeza mzigo kwenye huduma:
- bei ya juu - ya juu - bei ya juu kwa dakika.
- bei ya chini - bei ya chini - bei ya chini kwa dakika.
- Bei ya kwanza - bei ya kwanza - bei ya kwanza kwa dakika.
- Bei ya mwisho - bei ya mwisho - bei ya mwisho kwa dakika.
- saizi ya kwanza - saizi ya kwanza - saizi ya kwanza ya biashara kwa dakika.
- saizi ya mwisho - saizi ya mwisho - saizi ya mwisho ya biashara kwa dakika.
- numTrades - count i - idadi ya biashara kwa dakika.
- kiasi - saizi ya jumla - jumla ya saizi za biashara kwa dakika.
- pvolume - bei ya jumla - jumla ya bei kwa dakika, inahitajika kwa avgPrice.
- - Bei ya jumla ya mauzo* saizi - jumla ya kiasi cha miamala kwa dakika.
- avgPrice β pvolume%numTrades β bei ya wastani kwa dakika.
- avgSize - kiasi%numTrades - wastani wa ukubwa wa biashara kwa dakika.
- vwap - mauzo%kiasi - bei ya wastani kwa dakika iliyopimwa na saizi ya ununuzi.
- cumVolume - kiasi cha jumla - saizi iliyokusanywa ya shughuli kwa muda wote.
Wacha tujadili mara moja jambo moja lisilo dhahiri - jinsi ya kuanzisha safu hizi kwa mara ya kwanza na kwa kila dakika inayofuata. Baadhi ya safu wima za aina ya firstPrice lazima zianzishwe ili kubatilisha kila wakati; thamani yake haijafafanuliwa. Aina zingine za sauti lazima ziwekwe 0 kila wakati. Pia kuna safu wima zinazohitaji mbinu iliyounganishwa - kwa mfano, cumVolume lazima inakiliwe kutoka dakika iliyotangulia, na kwa ile ya kwanza iwe 0. Hebu tuweke vigezo hivi vyote kwa kutumia data ya kamusi. aina (sawa na rekodi):
// list ! list β ΡΠΎΠ·Π΄Π°ΡΡ ΡΠ»ΠΎΠ²Π°ΡΡ, 0n β float null, 0N β long null, `sym β ΡΠΈΠΏ ΡΠΈΠΌΠ²ΠΎΠ», `sym1`sym2 β ΡΠΏΠΈΡΠΎΠΊ ΡΠΈΠΌΠ²ΠΎΠ»ΠΎΠ²
initWith:`sym`time`high`low`firstPrice`lastPrice`firstSize`lastSize`numTrades`volume`pvolume`turnover`avgPrice`avgSize`vwap`cumVolume!(`;00:00;0n;0n;0n;0n;0N;0N;0;0;0.0;0.0;0n;0n;0n;0);
aggCols:reverse key[initWith] except `sym`time; // ΡΠΏΠΈΡΠΎΠΊ Π²ΡΠ΅Ρ
Π²ΡΡΠΈΡΠ»ΡΠ΅ΠΌΡΡ
ΠΊΠΎΠ»ΠΎΠ½ΠΎΠΊ, reverse ΠΎΠ±ΡΡΡΠ½Π΅Π½ Π½ΠΈΠΆΠ΅
Niliongeza sym na wakati kwa kamusi kwa urahisi, sasa initWith ni laini iliyotengenezwa tayari kutoka kwa jedwali la mwisho la jumla, ambapo inabaki kuweka sym sahihi na wakati. Unaweza kuitumia kuongeza safu mlalo mpya kwenye jedwali.
Tutahitaji aggCols wakati wa kuunda chaguo za kukokotoa. Orodha lazima igeuzwe kutokana na mpangilio wa maneno katika Q yanatathminiwa (kutoka kulia kwenda kushoto). Kusudi ni kuhakikisha hesabu inatoka juu hadi cumVolume, kwani safu wima zingine hutegemea zilizotangulia.
Safu ambazo zinahitaji kunakiliwa hadi dakika mpya kutoka kwa ile iliyotangulia, safu wima ya ulinganifu huongezwa kwa urahisi:
rollColumns:`sym`cumVolume;
Sasa hebu tugawanye safu katika vikundi kulingana na jinsi zinapaswa kusasishwa. Aina tatu zinaweza kutofautishwa:
- Wakusanyaji (kiasi, mauzo, ..) - lazima tuongeze thamani inayoingia kwa uliopita.
- Kwa hatua maalum (ya juu, ya chini, ..) - thamani ya kwanza katika dakika inachukuliwa kutoka kwa data inayoingia, wengine huhesabiwa kwa kutumia kazi.
- Pumzika. Hukokotoa kila wakati kwa kutumia chaguo za kukokotoa.
Wacha tufafanue anuwai za madarasa haya:
accumulatorCols:`numTrades`volume`pvolume`turnover;
specialCols:`high`low`firstPrice`firstSize;
Agizo la hesabu
Tutasasisha jedwali lililojumlishwa katika hatua mbili. Kwa ufanisi, kwanza tunapunguza meza inayoingia ili kuwe na safu moja tu kwa kila herufi na dakika. Ukweli kwamba kazi zetu zote ni za nyongeza na za ushirika huhakikisha kwamba matokeo ya hatua hii ya ziada hayatabadilika. Unaweza kupunguza meza kwa kutumia select:
select high:max price, low:min price β¦ by sym,time.minute from table
Njia hii ina hasara - seti ya nguzo zilizohesabiwa zimefafanuliwa. Kwa bahati nzuri, katika Q, select pia inatekelezwa kama kazi ambapo unaweza kubadilisha hoja zilizoundwa kwa nguvu:
?[table;whereClause;byClause;selectClause]
Sitaelezea kwa undani muundo wa hoja; kwa upande wetu, tu baada ya kuchagua misemo itakuwa isiyo ya maana na inapaswa kuwa kamusi za safu wima!maneno. Kwa hivyo, kazi ya kupungua inaweza kufafanuliwa kama ifuatavyo:
selExpression:`high`low`firstPrice`lastPrice`firstSize`lastSize`numTrades`volume`pvolume`turnover!parse each ("max price";"min price";"first price";"last price";"first size";"last size";"count i";"sum size";"sum price";"sum price*size"); // each ΡΡΠΎ ΡΡΠ½ΠΊΡΠΈΡ map Π² Q Π΄Π»Ρ ΠΎΠ΄Π½ΠΎΠ³ΠΎ ΡΠΏΠΈΡΠΊΠ°
preprocess:?[;();`sym`time!`sym`time.minute;selExpression];
Kwa uwazi, nilitumia chaguo la kukokotoa, ambalo hugeuza kamba na usemi wa Q kuwa thamani inayoweza kupitishwa kwa chaguo la kukokotoa la eval na inayohitajika katika chaguo la kukokotoa. Pia kumbuka kuwa preprocess inafafanuliwa kama makadirio (yaani, chaguo la kukokotoa lenye hoja zilizofafanuliwa sehemu) ya chaguo la kukokotoa, hoja moja (meza) haipo. Ikiwa tunatumia preprocess kwenye meza, tutapata meza iliyoshinikwa.
Hatua ya pili ni kusasisha jedwali lililojumlishwa. Wacha kwanza tuandike algorithm katika pseudocode:
for each sym in inputTable
idx: row index in agg table for sym+currentTime;
aggTable[idx;`high]: aggTable[idx;`high] | inputTable[sym;`high];
aggTable[idx;`volume]: aggTable[idx;`volume] + inputTable[sym;`volume];
β¦
Katika Q, ni kawaida kutumia ramani/punguza vitendaji badala ya vitanzi. Lakini kwa kuwa Q ni lugha ya vekta na tunaweza kutumia shughuli zote kwa urahisi kwa alama zote mara moja, basi kwa makadirio ya kwanza tunaweza kufanya bila kitanzi hata kidogo, tukifanya shughuli kwenye alama zote mara moja:
idx:calcIdx inputTable;
row:aggTable idx;
aggTable[idx;`high]: row[`high] | inputTable`high;
aggTable[idx;`volume]: row[`volume] + inputTable`volume;
β¦
Lakini tunaweza kwenda mbali zaidi, Q ina opereta wa kipekee na mwenye nguvu sana - opereta wa kazi ya jumla. Inakuruhusu kubadilisha seti ya maadili katika muundo changamano wa data kwa kutumia orodha ya fahirisi, kazi na hoja. Kwa upande wetu inaonekana kama hii:
idx:calcIdx inputTable;
rows:aggTable idx;
// .[target;(idx0;idx1;..);function;argument] ~ target[idx 0;idx 1;β¦]: function[target[idx 0;idx 1;β¦];argument], Π² Π½Π°ΡΠ΅ΠΌ ΡΠ»ΡΡΠ°Π΅ ΡΡΠ½ΠΊΡΠΈΡ β ΡΡΠΎ ΠΏΡΠΈΡΠ²Π°ΠΈΠ²Π°Π½ΠΈΠ΅
.[aggTable;(idx;aggCols);:;flip (row[`high] | inputTable`high;row[`volume] + inputTable`volume;β¦)];
Kwa bahati mbaya, ili kugawa jedwali unahitaji orodha ya safu mlalo, sio safu wima, na lazima ubadilishe matrix (orodha ya safu wima hadi orodha ya safu) kwa kutumia kitendakazi cha kugeuza. Hii ni ghali kwa jedwali kubwa, kwa hivyo badala yake tunatumia mgawo wa jumla kwa kila safu kando, kwa kutumia kazi ya ramani (ambayo inaonekana kama apostrofi):
.[aggTable;;:;]'[(idx;)each aggCols; (row[`high] | inputTable`high;row[`volume] + inputTable`volume;β¦)];
Tunatumia tena makadirio ya kazi. Pia kumbuka kuwa katika Q, kuunda orodha pia ni kazi na tunaweza kuiita kwa kutumia kila(ramani) kazi kupata orodha ya orodha.
Ili kuhakikisha kuwa seti ya safu wima zilizohesabiwa haijasanikishwa, tutaunda usemi ulio hapo juu kwa nguvu. Hebu kwanza tufafanue chaguo za kukokotoa ili kukokotoa kila safu, kwa kutumia safu mlalo na vigeu vya inp kurejelea data iliyojumlishwa na ingizo:
aggExpression:`high`low`firstPrice`lastPrice`firstSize`lastSize`avgPrice`avgSize`vwap`cumVolume!
("row[`high]|inp`high";"row[`low]&inp`low";"row`firstPrice";"inp`lastPrice";"row`firstSize";"inp`lastSize";"pvolume%numTrades";"volume%numTrades";"turnover%volume";"row[`cumVolume]+inp`volume");
Baadhi ya safu wima ni maalum; thamani yake ya kwanza haipaswi kuhesabiwa kwa chaguo za kukokotoa. Tunaweza kubainisha kuwa ni ya kwanza kwa safu mlalo[`numTrades] - ikiwa ina 0, basi thamani ni ya kwanza. Q ina chaguo la kukokotoa - ?[Boolean list;list1;list2] - ambayo huchagua thamani kutoka orodha ya 1 au 2 kulingana na hali katika hoja ya kwanza:
// high -> ?[isFirst;inp`high;row[`high]|inp`high]
// @ - ΡΠΎΠΆΠ΅ ΠΎΠ±ΠΎΠ±ΡΠ΅Π½Π½ΠΎΠ΅ ΠΏΡΠΈΡΠ²Π°ΠΈΠ²Π°Π½ΠΈΠ΅ Π΄Π»Ρ ΡΠ»ΡΡΠ°Ρ ΠΊΠΎΠ³Π΄Π° ΠΈΠ½Π΄Π΅ΠΊΡ Π½Π΅Π³Π»ΡΠ±ΠΎΠΊΠΈΠΉ
@[`aggExpression;specialCols;{[x;y]"?[isFirst;inp`",y,";",x,"]"};string specialCols];
Hapa niliita mgawo wa jumla na kazi yangu (msemo katika brashi za curly). Inapokea thamani ya sasa (hoja ya kwanza) na hoja ya ziada, ambayo ninapitisha katika paramu ya 4.
Wacha tuongeze spika za betri kando, kwani kazi ni sawa kwao:
// volume -> row[`volume]+inp`volume
aggExpression[accumulatorCols]:{"row[`",x,"]+inp`",x } each string accumulatorCols;
Huu ni mgawo wa kawaida kwa viwango vya Q, lakini ninakabidhi orodha ya maadili mara moja. Mwishowe, wacha tuunda kazi kuu:
// ":",/:aggExprs ~ map[{":",x};aggExpr] => ":row[`high]|inp`high" ΠΏΡΠΈΡΠ²ΠΎΠΈΠΌ Π²ΡΡΠΈΡΠ»Π΅Π½Π½ΠΎΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΠΎΠΉ, ΠΏΠΎΡΠΎΠΌΡ ΡΡΠΎ Π½Π΅ΠΊΠΎΡΠΎΡΡΠ΅ ΠΊΠΎΠ»ΠΎΠ½ΠΊΠΈ Π·Π°Π²ΠΈΡΡΡ ΠΎΡ ΡΠΆΠ΅ Π²ΡΡΠΈΡΠ»Π΅Π½Π½ΡΡ
Π·Π½Π°ΡΠ΅Π½ΠΈΠΉ
// string[cols],'exprs ~ map[,;string[cols];exprs] => "high:row[`high]|inp`high" Π·Π°Π²Π΅ΡΡΠΈΠΌ ΡΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡΠΈΡΠ²Π°ΠΈΠ²Π°Π½ΠΈΡ. ,β ΡΠ°ΡΡΠΈΡΡΠΎΠ²ΡΠ²Π°Π΅ΡΡΡ ΠΊΠ°ΠΊ map[concat]
// ";" sv exprs β String from Vector (sv), ΡΠΎΠ΅Π΄ΠΈΠ½ΡΠ΅Ρ ΡΠΏΠΈΡΠΎΠΊ ΡΡΡΠΎΠΊ Π²ΡΡΠ°Π²Π»ΡΡ β;β ΠΏΠΎΡΡΠ΅Π΄ΠΈΠ½Π΅
updateAgg:value "{[aggTable;idx;inp] row:aggTable idx; isFirst_0=row`numTrades; .[aggTable;;:;]'[(idx;)each aggCols;(",(";"sv string[aggCols],'":",/:aggExpression aggCols),")]}";
Kwa usemi huu, ninaunda kitendakazi kwa nguvu kutoka kwa kamba ambayo ina usemi niliotoa hapo juu. Matokeo yake yataonekana kama hii:
{[aggTable;idx;inp] rows:aggTable idx; isFirst_0=row`numTrades; .[aggTable;;:;]'[(idx;)each aggCols ;(cumVolume:row[`cumVolume]+inp`cumVolume;β¦ ; high:?[isFirst;inp`high;row[`high]|inp`high])]}
Agizo la tathmini ya safu wima limegeuzwa kwa sababu katika Q mpangilio wa tathmini ni kutoka kulia kwenda kushoto.
Sasa tuna kazi kuu mbili muhimu kwa mahesabu, tunahitaji tu kuongeza miundombinu kidogo na huduma iko tayari.
Hatua za mwisho
Tuna preprocess na updateAgg kazi ambayo hufanya kazi yote. Lakini bado ni muhimu kuhakikisha mpito sahihi kupitia dakika na kuhesabu faharisi za kujumlisha. Kwanza kabisa, hebu tufafanue kazi ya init:
init:{
tradeAgg:: 0#enlist[initWith]; // ΡΠΎΠ·Π΄Π°Π΅ΠΌ ΠΏΡΡΡΡΡ ΡΠΈΠΏΠΈΠ·ΠΈΡΠΎΠ²Π°Π½Π½ΡΡ ΡΠ°Π±Π»ΠΈΡΡ, enlist ΠΏΡΠ΅Π²ΡΠ°ΡΠ°Π΅Ρ ΡΠ»ΠΎΠ²Π°ΡΡ Π² ΡΠ°Π±Π»ΠΈΡΡ, Π° 0# ΠΎΠ·Π½Π°ΡΠ°Π΅Ρ Π²Π·ΡΡΡ 0 ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² ΠΈΠ· Π½Π΅Π΅
currTime::00:00; // Π½Π°ΡΠ½Π΅ΠΌ Ρ 0, :: ΠΎΠ·Π½Π°ΡΠ°Π΅Ρ, ΡΡΠΎ ΠΏΡΠΈΡΠ²Π°ΠΈΠ²Π°Π½ΠΈΠ΅ Π² Π³Π»ΠΎΠ±Π°Π»ΡΠ½ΡΡ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΡ
currSyms::`u#`symbol$(); // `u# - ΠΏΡΠ΅Π²ΡΠ°ΡΠ°Π΅Ρ ΡΠΏΠΈΡΠΎΠΊ Π² Π΄Π΅ΡΠ΅Π²ΠΎ, Π΄Π»Ρ ΡΡΠΊΠΎΡΠ΅Π½ΠΈΡ ΠΏΠΎΠΈΡΠΊΠ° ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ²
offset::0; // ΠΈΠ½Π΄Π΅ΠΊΡ Π² tradeAgg, Π³Π΄Π΅ Π½Π°ΡΠΈΠ½Π°Π΅ΡΡΡ ΡΠ΅ΠΊΡΡΠ°Ρ ΠΌΠΈΠ½ΡΡΠ°
rollCache:: `sym xkey update `u#sym from rollColumns#tradeAgg; // ΠΊΡΡ Π΄Π»Ρ ΠΏΠΎΡΠ»Π΅Π΄Π½ΠΈΡ
Π·Π½Π°ΡΠ΅Π½ΠΈΠΉ roll ΠΊΠΎΠ»ΠΎΠ½ΠΎΠΊ, ΡΠ°Π±Π»ΠΈΡΠ° Ρ ΠΊΠ»ΡΡΠΎΠΌ sym
}
Pia tutafafanua kazi ya roll, ambayo itabadilisha dakika ya sasa:
roll:{[tm]
if[currTime>tm; :init[]]; // Π΅ΡΠ»ΠΈ ΠΏΠ΅ΡΠ΅Π²Π°Π»ΠΈΠ»ΠΈ Π·Π° ΠΏΠΎΠ»Π½ΠΎΡΡ, ΡΠΎ ΠΏΡΠΎΡΡΠΎ Π²ΡΠ·ΠΎΠ²Π΅ΠΌ init
rollCache,::offset _ rollColumns#tradeAgg; // ΠΎΠ±Π½ΠΎΠ²ΠΈΠΌ ΠΊΡΡ β Π²Π·ΡΡΡ roll ΠΊΠΎΠ»ΠΎΠ½ΠΊΠΈ ΠΈΠ· aggTable, ΠΎΠ±ΡΠ΅Π·Π°ΡΡ, Π²ΡΡΠ°Π²ΠΈΡΡ Π² rollCache
offset::count tradeAgg;
currSyms::`u#`$();
}
Tutahitaji chaguo za kukokotoa ili kuongeza herufi mpya:
addSyms:{[syms]
currSyms,::syms; // Π΄ΠΎΠ±Π°Π²ΠΈΠΌ Π² ΡΠΏΠΈΡΠΎΠΊ ΠΈΠ·Π²Π΅ΡΡΠ½ΡΡ
// Π΄ΠΎΠ±Π°Π²ΠΈΠΌ Π² ΡΠ°Π±Π»ΠΈΡΡ sym, time ΠΈ rollColumns Π²ΠΎΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π²ΡΠΈΡΡ ΠΎΠ±ΠΎΠ±ΡΠ΅Π½Π½ΡΠΌ ΠΏΡΠΈΡΠ²Π°ΠΈΠ²Π°Π½ΠΈΠ΅ΠΌ.
// Π€ΡΠ½ΠΊΡΠΈΡ ^ ΠΏΠΎΠ΄ΡΡΠ°Π²Π»ΡΠ΅Ρ Π·Π½Π°ΡΠ΅Π½ΠΈΡ ΠΏΠΎ ΡΠΌΠΎΠ»ΡΠ°Π½ΠΈΡ Π΄Π»Ρ roll ΠΊΠΎΠ»ΠΎΠ½ΠΎΠΊ, Π΅ΡΠ»ΠΈ ΡΠΈΠΌΠ²ΠΎΠ»Π° Π½Π΅Ρ Π² ΠΊΡΡΠ΅. value flip table Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ ΡΠΏΠΈΡΠΎΠΊ ΠΊΠΎΠ»ΠΎΠ½ΠΎΠΊ Π² ΡΠ°Π±Π»ΠΈΡΠ΅.
`tradeAgg upsert @[count[syms]#enlist initWith;`sym`time,cols rc;:;(syms;currTime), (initWith cols rc)^value flip rc:rollCache ([] sym: syms)];
}
Na mwishowe, kazi ya kusasisha (jina la kitamaduni la kazi hii kwa huduma za Q), ambayo inaitwa na mteja kuongeza data:
upd:{[tblName;data] // tblName Π½Π°ΠΌ Π½Π΅ Π½ΡΠΆΠ½ΠΎ, Π½ΠΎ ΠΎΠ±ΡΡΠ½ΠΎ ΡΠ΅ΡΠ²ΠΈΡ ΠΎΠ±ΡΠ°Π±Π°ΡΡΠ²Π°Π΅Ρ Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΎ ΡΠ°Π±Π»ΠΈΡ
tm:exec distinct time from data:() xkey preprocess data; // preprocess & calc time
updMinute[data] each tm; // Π΄ΠΎΠ±Π°Π²ΠΈΠΌ Π΄Π°Π½Π½ΡΠ΅ Π΄Π»Ρ ΠΊΠ°ΠΆΠ΄ΠΎΠΉ ΠΌΠΈΠ½ΡΡΡ
};
updMinute:{[data;tm]
if[tm<>currTime; roll tm; currTime::tm]; // ΠΏΠΎΠΌΠ΅Π½ΡΠ΅ΠΌ ΠΌΠΈΠ½ΡΡΡ, Π΅ΡΠ»ΠΈ Π½Π΅ΠΎΠ±Ρ
ΠΎΠ΄ΠΈΠΌΠΎ
data:select from data where time=tm; // ΡΠΈΠ»ΡΡΡΠ°ΡΠΈΡ
if[count msyms:syms where not (syms:data`sym)in currSyms; addSyms msyms]; // Π½ΠΎΠ²ΡΠ΅ ΡΠΈΠΌΠ²ΠΎΠ»Ρ
updateAgg[`tradeAgg;offset+currSyms?syms;data]; // ΠΎΠ±Π½ΠΎΠ²ΠΈΠΌ Π°Π³ΡΠ΅Π³ΠΈΡΠΎΠ²Π°Π½Π½ΡΡ ΡΠ°Π±Π»ΠΈΡΡ. Π€ΡΠ½ΠΊΡΠΈΡ ? ΠΈΡΠ΅Ρ ΠΈΠ½Π΄Π΅ΠΊΡ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² ΡΠΏΠΈΡΠΊΠ° ΡΠΏΡΠ°Π²Π° Π² ΡΠΏΠΈΡΠΊΠ΅ ΡΠ»Π΅Π²Π°.
};
Ni hayo tu. Hapa kuna nambari kamili ya huduma yetu, kama ilivyoahidiwa, mistari michache tu:
initWith:`sym`time`high`low`firstPrice`lastPrice`firstSize`lastSize`numTrades`volume`pvolume`turnover`avgPrice`avgSize`vwap`cumVolume!(`;00:00;0n;0n;0n;0n;0N;0N;0;0;0.0;0.0;0n;0n;0n;0);
aggCols:reverse key[initWith] except `sym`time;
rollColumns:`sym`cumVolume;
accumulatorCols:`numTrades`volume`pvolume`turnover;
specialCols:`high`low`firstPrice`firstSize;
selExpression:`high`low`firstPrice`lastPrice`firstSize`lastSize`numTrades`volume`pvolume`turnover!parse each ("max price";"min price";"first price";"last price";"first size";"last size";"count i";"sum size";"sum price";"sum price*size");
preprocess:?[;();`sym`time!`sym`time.minute;selExpression];
aggExpression:`high`low`firstPrice`lastPrice`firstSize`lastSize`avgPrice`avgSize`vwap`cumVolume!("row[`high]|inp`high";"row[`low]&inp`low";"row`firstPrice";"inp`lastPrice";"row`firstSize";"inp`lastSize";"pvolume%numTrades";"volume%numTrades";"turnover%volume";"row[`cumVolume]+inp`volume");
@[`aggExpression;specialCols;{"?[isFirst;inp`",y,";",x,"]"};string specialCols];
aggExpression[accumulatorCols]:{"row[`",x,"]+inp`",x } each string accumulatorCols;
updateAgg:value "{[aggTable;idx;inp] row:aggTable idx; isFirst_0=row`numTrades; .[aggTable;;:;]'[(idx;)each aggCols;(",(";"sv string[aggCols],'":",/:aggExpression aggCols),")]}"; / '
init:{
tradeAgg::0#enlist[initWith];
currTime::00:00;
currSyms::`u#`symbol$();
offset::0;
rollCache:: `sym xkey update `u#sym from rollColumns#tradeAgg;
};
roll:{[tm]
if[currTime>tm; :init[]];
rollCache,::offset _ rollColumns#tradeAgg;
offset::count tradeAgg;
currSyms::`u#`$();
};
addSyms:{[syms]
currSyms,::syms;
`tradeAgg upsert @[count[syms]#enlist initWith;`sym`time,cols rc;:;(syms;currTime),(initWith cols rc)^value flip rc:rollCache ([] sym: syms)];
};
upd:{[tblName;data] updMinute[data] each exec distinct time from data:() xkey preprocess data};
updMinute:{[data;tm]
if[tm<>currTime; roll tm; currTime::tm];
data:select from data where time=tm;
if[count msyms:syms where not (syms:data`sym)in currSyms; addSyms msyms];
updateAgg[`tradeAgg;offset+currSyms?syms;data];
};
Upimaji
Wacha tuangalie utendaji wa huduma. Ili kufanya hivyo, wacha tuiendeshe kwa mchakato tofauti (weka nambari kwenye faili ya service.q) na uite kazi ya init:
q service.q βp 5566
q)init[]
Kwenye koni nyingine, anza mchakato wa pili wa Q na uunganishe na ya kwanza:
h:hopen `:host:5566
h:hopen 5566 // Π΅ΡΠ»ΠΈ ΠΎΠ±Π° Π½Π° ΠΎΠ΄Π½ΠΎΠΌ Ρ
ΠΎΡΡΠ΅
Kwanza, hebu tutengeneze orodha ya alama - vipande 10000 na kuongeza kazi ili kuunda meza ya random. Katika console ya pili:
syms:`IBM`AAPL`GOOG,-9997?`8
rnd:{[n;t] ([] sym:n?syms; time:t+asc n#til 25; price:n?10f; size:n?10)}
Niliongeza alama tatu halisi kwenye orodha ili iwe rahisi kuzitafuta kwenye jedwali. Chaguo za kukokotoa za rnd huunda jedwali nasibu lenye safu mlalo n, ambapo muda hutofautiana kutoka t hadi t+25 milisekunde.
Sasa unaweza kujaribu kutuma data kwa huduma (ongeza saa kumi za kwanza):
{h (`upd;`trade;rnd[10000;x])} each `time$00:00 + til 60*10
Unaweza kuangalia katika huduma ambayo jedwali limesasishwa:
c 25 200
select from tradeAgg where sym=`AAPL
-20#select from tradeAgg where sym=`AAPL
Matokeo:
sym|time|high|low|firstPrice|lastPrice|firstSize|lastSize|numTrades|volume|pvolume|turnover|avgPrice|avgSize|vwap|cumVolume
--|--|--|--|--|--------------------------------
AAPL|09:27|9.258904|9.258904|9.258904|9.258904|8|8|1|8|9.258904|74.07123|9.258904|8|9.258904|2888
AAPL|09:28|9.068162|9.068162|9.068162|9.068162|7|7|1|7|9.068162|63.47713|9.068162|7|9.068162|2895
AAPL|09:31|4.680449|0.2011121|1.620827|0.2011121|1|5|4|14|9.569556|36.84342|2.392389|3.5|2.631673|2909
AAPL|09:33|2.812535|2.812535|2.812535|2.812535|6|6|1|6|2.812535|16.87521|2.812535|6|2.812535|2915
AAPL|09:34|5.099025|5.099025|5.099025|5.099025|4|4|1|4|5.099025|20.3961|5.099025|4|5.099025|2919
Wacha sasa tufanye upimaji wa mzigo ili kujua ni data ngapi ambayo huduma inaweza kuchakata kwa dakika. Acha nikukumbushe kwamba tuliweka muda wa kusasisha hadi milisekunde 25. Kwa hivyo, huduma lazima (kwa wastani) ilingane na angalau milisekunde 20 kwa kila sasisho ili kuwapa watumiaji muda wa kuomba data. Ingiza yafuatayo katika mchakato wa pili:
tm:10:00:00.000
stressTest:{[n] 1 string[tm]," "; times,::h ({st:.z.T; upd[`trade;x]; .z.T-st};rnd[n;tm]); tm+:25}
start:{[n] times::(); do[4800;stressTest[n]]; -1 " "; `min`avg`med`max!(min times;avg times;med times;max times)}
4800 ni dakika mbili. Unaweza kujaribu kukimbia kwanza kwa safu mlalo 1000 kila baada ya milisekunde 25:
start 1000
Kwa upande wangu, matokeo yake ni karibu na milisekunde kadhaa kwa kila sasisho. Kwa hivyo nitaongeza mara moja idadi ya safu hadi 10.000:
start 10000
Matokeo:
min| 00:00:00.004
avg| 9.191458
med| 9f
max| 00:00:00.030
Tena, hakuna kitu maalum, lakini hii ni mistari milioni 24 kwa dakika, elfu 400 kwa sekunde. Kwa zaidi ya milisekunde 25, sasisho lilipungua mara 5 tu, inaonekana wakati dakika ilibadilika. Wacha tuongeze hadi 100.000:
start 100000
Matokeo:
min| 00:00:00.013
avg| 25.11083
med| 24f
max| 00:00:00.108
q)sum times
00:02:00.532
Kama unavyoona, huduma haiwezi kustahimili, lakini hata hivyo inafanikiwa kusalia. Kiasi kama hicho cha data (safu milioni 240 kwa dakika) ni kubwa sana; katika hali kama hizi, ni kawaida kuzindua clones kadhaa (au hata clones kadhaa) za huduma, ambayo kila moja husindika sehemu tu ya wahusika. Bado, matokeo yake ni ya kuvutia kwa lugha iliyotafsiriwa ambayo inalenga hasa kuhifadhi data.
Swali linaweza kutokea kwa nini wakati hukua bila mpangilio na saizi ya kila sasisho. Sababu ni kwamba kitendakazi cha shrink kwa kweli ni kazi ya C, ambayo ni bora zaidi kuliko updateAgg. Kuanzia saizi fulani ya sasisho (karibu 10.000), sasishoAgg hufikia dari yake na kisha wakati wake wa utekelezaji hautegemei saizi ya sasisho. Ni kutokana na hatua ya awali Q kwamba huduma inaweza kuchimba kiasi kama hicho cha data. Hii inaangazia jinsi ilivyo muhimu kuchagua algoriti sahihi unapofanya kazi na data kubwa. Jambo lingine ni uhifadhi sahihi wa data kwenye kumbukumbu. Ikiwa data haikuhifadhiwa kwa safu au haikuagizwa kwa wakati, basi tungefahamiana na kitu kama kukosa kashe ya TLB - kutokuwepo kwa anwani ya ukurasa wa kumbukumbu kwenye kashe ya anwani ya kichakataji. Kutafuta anwani huchukua takriban mara 30 zaidi ikiwa haifaulu, na ikiwa data imetawanyika, inaweza kupunguza kasi ya huduma mara kadhaa.
Hitimisho
Katika nakala hii, nilionyesha kuwa hifadhidata ya KDB+ na Q zinafaa sio tu kwa kuhifadhi data kubwa na kuipata kwa urahisi kupitia chaguo, lakini pia kwa kuunda huduma za usindikaji wa data ambazo zina uwezo wa kusaga mamia ya mamilioni ya safu/gigabytes ya data hata katika mchakato mmoja wa Q. Lugha ya Q yenyewe inaruhusu utekelezaji mafupi na mzuri sana wa algoriti zinazohusiana na usindikaji wa data kwa sababu ya asili yake ya vekta, mkalimani wa lahaja wa SQL na seti iliyofanikiwa sana ya kazi za maktaba.
Nitagundua kuwa yaliyo hapo juu ni sehemu tu ya kile Q inaweza kufanya, ina sifa zingine za kipekee pia. Kwa mfano, itifaki rahisi sana ya IPC ambayo inafuta mpaka kati ya michakato ya mtu binafsi ya Q na kukuruhusu kuchanganya mamia ya michakato hii hadi mtandao mmoja, ambao unaweza kupatikana kwenye seva nyingi katika sehemu tofauti za ulimwengu.
Chanzo: mapenzi.com