የእውነተኛ ጊዜ አገልግሎት ምሳሌን በመጠቀም የQ እና የKDB+ ቋንቋ ባህሪዎች

ስለ KDB+ መሰረት፣ የQ ፕሮግራሚንግ ቋንቋ ምን እንደሆነ፣ በቀድሞዬ ውስጥ ምን ጥንካሬ እና ድክመቶች እንዳሉ ማንበብ ትችላለህ። ጽሑፍ እና በመግቢያው ላይ በአጭሩ። በአንቀጹ ውስጥ ገቢውን የውሂብ ዥረት የሚያስኬድ እና በየደቂቃው በ "እውነተኛ ጊዜ" ሁነታ (ማለትም ከቀጣዩ የውሂብ ክፍል በፊት ሁሉንም ነገር ለማስላት ጊዜ ይኖረዋል) የሚያገለግል አገልግሎትን በ Q ላይ እንተገብራለን። የQ ዋናው ገጽታ በነጠላ ነገሮች ሳይሆን በሥርዓታቸው፣ በተደራጁ ድርድሮች እና ሌሎች ውስብስብ ነገሮች እንዲሠሩ የሚያስችልዎ የቬክተር ቋንቋ ነው። እንደ ኪ እና ዘመዶቹ K, J, APL ያሉ ቋንቋዎች በአጭር አነጋገር ታዋቂዎች ናቸው. ብዙ ጊዜ እንደ ጃቫ ባሉ በሚታወቅ ቋንቋ በርካታ የኮድ ስክሪን የሚይዝ ፕሮግራም በጥቂት መስመሮች ውስጥ ሊጻፍባቸው ይችላል። በዚህ ጽሑፍ ውስጥ ማሳየት የምፈልገው ይህንን ነው።

የእውነተኛ ጊዜ አገልግሎት ምሳሌን በመጠቀም የQ እና የKDB+ ቋንቋ ባህሪዎች

መግቢያ

KDB+ በተወሰነ መንገድ (በዋነኛነት በጊዜ) በከፍተኛ መጠን ላይ ያተኮረ የአምድ ዳታቤዝ ነው። በዋናነት በፋይናንሺያል ተቋማት - ባንኮች, የኢንቨስትመንት ፈንድ, የኢንሹራንስ ኩባንያዎች ውስጥ ጥቅም ላይ ይውላል. የQ ቋንቋ የKDB+ ውስጣዊ ቋንቋ ሲሆን ከዚህ ውሂብ ጋር በብቃት እንዲሰሩ ያስችልዎታል። የ Q ርዕዮተ ዓለም አጭር እና ቅልጥፍና ሲሆን ግልጽነት ግን መስዋእትነት ነው። ይህ የተረጋገጠው በማንኛውም ሁኔታ የቬክተር ቋንቋን ለመረዳት አስቸጋሪ እንደሚሆን እና የቀረጻው አጭርነት እና ብልጽግና የፕሮግራሙን በጣም ትልቅ ክፍል በአንድ ማያ ገጽ ላይ እንዲያዩ ያስችልዎታል ፣ ይህም በመጨረሻ ለመረዳት ቀላል ያደርገዋል።

በዚህ ጽሑፍ ውስጥ በ Q ውስጥ የተሟላ ፕሮግራም እንተገብራለን እና ሊሞክሩት ይችላሉ። ይህንን ለማድረግ ትክክለኛውን Q ያስፈልግዎታል ነፃ 32-ቢት ስሪት በ kx ኩባንያ ድረ-ገጽ ላይ ማውረድ ይችላሉ - www.kx.com. እዚያ, ፍላጎት ካሎት, በመጽሐፉ Q ላይ የማመሳከሪያ መረጃ ያገኛሉ ጥ ለሟቾች እና በዚህ ርዕስ ላይ የተለያዩ ጽሑፎች.

የችግሩ ቀመር

በየ25 ሚሊሰከንድ መረጃ ያለው ሠንጠረዥ የሚልክ ምንጭ አለ። KDB+ በዋናነት በፋይናንስ ውስጥ ጥቅም ላይ የሚውል ስለሆነ, ይህ የግብይቶች ሰንጠረዥ ነው ብለን እንገምታለን, እሱም የሚከተሉት ዓምዶች አሉት: ጊዜ (ጊዜ በሚሊሰከንዶች), ሲም (በአክሲዮን ልውውጥ ላይ የኩባንያው ስያሜ -) IBM, AAPL,…), ዋጋ (አክሲዮኖቹ የተገዙበት ዋጋ), መጠን (የግብይቱ መጠን). የ 25 ሚሊሰከንድ ክፍተት የዘፈቀደ ነው, በጣም ትንሽ እና ረጅም አይደለም. የእሱ መገኘት ማለት ውሂቡ ቀድሞውኑ ወደ ተያዘው አገልግሎት ይመጣል ማለት ነው። አሁን ባለው ጭነት ላይ በመመስረት ተለዋዋጭ ማቋረጦችን ጨምሮ በአገልግሎት በኩል ማቋትን መተግበር ቀላል ይሆናል ፣ ግን ለቀላል ፣ እኛ በቋሚ ክፍተት ላይ እናተኩራለን።

አገልግሎቱ ከሲም አምድ ለእያንዳንዱ የገቢ ምልክት በየደቂቃው መቁጠር አለበት የማጠቃለያ ተግባራት - ከፍተኛ ዋጋ፣ አማካይ ዋጋ፣ ድምር መጠን፣ ወዘተ. ጠቃሚ መረጃ. ለቀላልነት, ሁሉም ተግባራት በእድገት ሊሰሉ እንደሚችሉ እንገምታለን, ማለትም. አዲስ እሴት ለማግኘት ሁለት ቁጥሮችን ማወቅ በቂ ነው - አሮጌው እና መጪ እሴቶች. ለምሳሌ፣ ተግባራቶቹ ከፍተኛ፣ አማካኝ፣ ድምር ይህ ንብረት አላቸው፣ ግን መካከለኛው ተግባር ግን የለውም።

እንዲሁም የገቢው የውሂብ ዥረት የታዘዘ ጊዜ ነው ብለን እንገምታለን። ይህ በመጨረሻው ደቂቃ ብቻ ለመስራት እድል ይሰጠናል. በተግባር ፣ አንዳንድ ዝመናዎች ዘግይተው ከሆነ ከአሁኑ እና ከቀደሙት ደቂቃዎች ጋር መሥራት መቻል በቂ ነው። ለቀላልነት, ይህንን ጉዳይ አንመለከትም.

የማዋሃድ ተግባራት

የሚፈለጉት የመደመር ተግባራት ከዚህ በታች ተዘርዝረዋል። በአገልግሎቱ ላይ ሸክሙን ለመጨመር በተቻለ መጠን ብዙዎቹን ወስጃለሁ-

  • ከፍተኛ - ከፍተኛ ዋጋ - ከፍተኛ ዋጋ በደቂቃ.
  • ዝቅተኛ - ዝቅተኛ ዋጋ - ዝቅተኛ ዋጋ በደቂቃ.
  • firstPrice - የመጀመሪያ ዋጋ - የመጀመሪያው ዋጋ በደቂቃ.
  • የመጨረሻ ዋጋ - የመጨረሻው ዋጋ - የመጨረሻው ዋጋ በደቂቃ.
  • የመጀመሪያ መጠን - የመጀመሪያ መጠን - የመጀመሪያው የንግድ መጠን በደቂቃ።
  • የመጨረሻው መጠን - የመጨረሻው መጠን - የመጨረሻው የንግድ መጠን በደቂቃ ውስጥ።
  • numTrades - ቁጥሩ i - የንግዶች ብዛት በደቂቃ።
  • መጠን - ድምር መጠን - የንግድ መጠኖች ድምር በደቂቃ.
  • pvolume - ድምር ዋጋ - የዋጋ ድምር በደቂቃ፣ ለአማካኝ ዋጋ ያስፈልጋል።
  • - ድምር ማዞሪያ ዋጋ * መጠን - አጠቃላይ የግብይቶች መጠን በደቂቃ።
  • አማካይ ዋጋ - pvolume%numTrades - አማካይ ዋጋ በደቂቃ።
  • አማካይ መጠን - መጠን%numTrades - አማካይ የንግድ መጠን በደቂቃ።
  • vwap - የመቀየሪያ% መጠን - አማካይ ዋጋ በደቂቃ በግብይት መጠን ይመዘናል።
  • ድምር - ድምር መጠን - በጠቅላላው ጊዜ የተጠራቀመ የግብይቶች መጠን።

ወዲያውኑ አንድ ግልጽ ያልሆነ ነጥብ እንወያይ - እነዚህን አምዶች ለመጀመሪያ ጊዜ እና ለእያንዳንዱ ቀጣይ ደቂቃ እንዴት ማስጀመር እንደሚቻል። አንዳንድ የአንደኛ ዋጋ ዓይነት አምዶች በእያንዳንዱ ጊዜ እንዲሻሩ መጀመር አለባቸው፤ ዋጋቸው ያልተገለጸ ነው። ሌሎች የድምጽ ዓይነቶች ሁልጊዜ ወደ 0 መዋቀር አለባቸው. በተጨማሪም የተጣመረ አቀራረብን የሚጠይቁ ዓምዶች አሉ - ለምሳሌ, cumVolume ካለፈው ደቂቃ መቅዳት አለበት, እና ለመጀመሪያ ጊዜ ወደ 0 ተቀናጅቷል. እነዚህን ሁሉ መመዘኛዎች የመዝገበ ቃላቱን ውሂብ በመጠቀም እናስቀምጥ. ዓይነት (ከመዝገብ ጋር ተመሳሳይ)

// 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 объяснен ниже

ለመመቻቸት ወደ መዝገበ-ቃላቱ ላይ ሲም እና ጊዜ ጨምሬያለሁ፣ አሁን initWith ከመጨረሻው የተጠራቀመ ጠረጴዛ ላይ ዝግጁ የሆነ መስመር ነው፣ እሱም ትክክለኛውን ሲም እና ጊዜ ለማዘጋጀት ይቀራል። አዲስ ረድፎችን ወደ ጠረጴዛ ለመጨመር ሊጠቀሙበት ይችላሉ.

የመደመር ተግባርን ስንፈጥር aggCols ያስፈልጉናል። በ Q ውስጥ ያሉ አባባሎች በሚገመገሙበት ቅደም ተከተል (ከቀኝ ወደ ግራ) ዝርዝሩ መገለበጥ አለበት። ግቡ ስሌቱ ከከፍተኛ ወደ ድምር ድምጽ መሄዱን ማረጋገጥ ነው፣ ምክንያቱም አንዳንድ ዓምዶች በቀድሞዎቹ ላይ ስለሚመሰረቱ።

ካለፈው ደቂቃ ወደ አዲስ ደቂቃ መቅዳት የሚያስፈልጋቸው አምዶች፣ ለሚመች ሲባል የሲም አምድ ተጨምሯል።

rollColumns:`sym`cumVolume;

አሁን ዓምዶቹን እንዴት ማዘመን እንዳለባቸው በቡድን እንከፋፍላቸው። ሶስት ዓይነቶችን መለየት ይቻላል-

  1. Accumulators (ድምጽ, ማዞሪያ, ..) - መጪውን እሴት ወደ ቀዳሚው መጨመር አለብን.
  2. በልዩ ነጥብ (ከፍተኛ, ዝቅተኛ, ..) - በደቂቃ ውስጥ የመጀመሪያው እሴት ከገቢው መረጃ ይወሰዳል, የተቀሩት ደግሞ ተግባሩን በመጠቀም ይሰላሉ.
  3. እረፍት ሁልጊዜ ተግባርን በመጠቀም ይሰላል።

ለእነዚህ ክፍሎች ተለዋዋጮችን እንግለጽ፡-

accumulatorCols:`numTrades`volume`pvolume`turnover;
specialCols:`high`low`firstPrice`firstSize;

ስሌት ቅደም ተከተል

የተዋሃደውን ሰንጠረዥ በሁለት ደረጃዎች እናዘምነዋለን. ለውጤታማነት ፣ ለእያንዳንዱ ቁምፊ እና ደቂቃ አንድ ረድፍ ብቻ እንዲኖር በመጀመሪያ መጪውን ጠረጴዛ እንቀንሳለን። ሁሉም የእኛ ተግባራቶች መጨመር እና ተባባሪ መሆናቸው የዚህ ተጨማሪ እርምጃ ውጤት እንደማይለወጥ ዋስትና ይሰጣል. ምረጥን በመጠቀም ጠረጴዛውን መቀነስ ይችላሉ-

select high:max price, low:min price … by sym,time.minute from table

ይህ ዘዴ ጉዳት አለው - የተሰሉ ዓምዶች ስብስብ አስቀድሞ ተወስኗል. እንደ እድል ሆኖ፣ በQ፣ ምረጥ በተለዋዋጭ የተፈጠሩ ነጋሪ እሴቶችን መተካት የምትችልበት ተግባር ሆኖ ተተግብሯል።

?[table;whereClause;byClause;selectClause]

የክርክሩን ፎርማት በዝርዝር አልገልጽም፤ በእኛ ሁኔታ አገላለጾችን መምረጥ እና መምረጥ ቀላል ያልሆኑ ይሆናሉ እናም የቅጽ ዓምዶች መዝገበ-ቃላት መሆን አለባቸው! ስለዚህ የመቀነስ ተግባር እንደሚከተለው ሊገለፅ ይችላል-

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

ግልፅ ለማድረግ፣ የመተንተን ተግባር ተጠቀምኩኝ፣ እሱም ከQ አገላለጽ ጋር ሕብረቁምፊ ወደ ኢቫል ተግባር ሊተላለፍ የሚችል እና በተግባሩ ምረጥ ውስጥ ወደ ሚፈለገው እሴት ይቀይራል። እንዲሁም ቅድመ-ሂደቱ እንደ ትንበያ (ማለትም በከፊል የተገለጹ ነጋሪ እሴቶች ያለው ተግባር) የመምረጥ ተግባር ተብሎ እንደሚገለጽ ልብ ይበሉ ፣ አንድ ነጋሪ እሴት (ሰንጠረዡ) ይጎድላል። በጠረጴዛ ላይ ቅድመ-ሂደትን ተግባራዊ ካደረግን, የታመቀ ጠረጴዛ እናገኛለን.

ሁለተኛው ደረጃ የተዋሃደውን ሰንጠረዥ ማዘመን ነው. መጀመሪያ አልጎሪዝምን በ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];
  …

በ Q ውስጥ ከ loops ይልቅ የካርታ / የመቀነስ ተግባራትን መጠቀም የተለመደ ነው. ነገር ግን Q የቬክተር ቋንቋ ስለሆነ እና ሁሉንም ኦፕሬሽኖች በአንድ ጊዜ በሁሉም ምልክቶች ላይ በቀላሉ መተግበር ስለምንችል በመጀመሪያ ግምታዊነት ሁሉንም ምልክቶችን በአንድ ጊዜ በማከናወን ያለ ሉፕ ማድረግ እንችላለን።

idx:calcIdx inputTable;
row:aggTable idx;
aggTable[idx;`high]: row[`high] | inputTable`high;
aggTable[idx;`volume]: row[`volume] + inputTable`volume;
…

ግን የበለጠ መሄድ እንችላለን, Q ልዩ እና እጅግ በጣም ኃይለኛ ኦፕሬተር አለው - አጠቃላይ የምደባ ኦፕሬተር. የመረጃ ጠቋሚዎችን ፣ ተግባሮችን እና ነጋሪ እሴቶችን በመጠቀም ውስብስብ በሆነ የውሂብ መዋቅር ውስጥ የእሴቶችን ስብስብ እንዲቀይሩ ያስችልዎታል። በእኛ ሁኔታ, ይህ ይመስላል:

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;…)];

እንደ አለመታደል ሆኖ ወደ ጠረጴዛ ለመመደብ የአምዶች ሳይሆን የረድፎች ዝርዝር ያስፈልግዎታል እና ማትሪክስ (የአምዶች ዝርዝር ወደ ረድፎች ዝርዝር) የመገልበጥ ተግባሩን በመጠቀም ማስተላለፍ አለብዎት። ይህ ለትልቅ ጠረጴዛ ውድ ነው፣ስለዚህ በምትኩ የካርታውን ተግባር (አፖስትሮፍ የሚመስለውን) በመጠቀም ለእያንዳንዱ አምድ አንድን አጠቃላይ ምደባ ለየብቻ እንተገብራለን።

.[aggTable;;:;]'[(idx;)each aggCols; (row[`high] | inputTable`high;row[`volume] + inputTable`volume;…)];

እንደገና የተግባር ትንበያ እንጠቀማለን. እንዲሁም በ Q ውስጥ ዝርዝር መፍጠር እንዲሁ ተግባር መሆኑን እና የዝርዝሮችን ዝርዝር ለማግኘት የእያንዳንዱን(ካርታ) ተግባር በመጠቀም ልንጠራው እንችላለን።

የተሰሉ ዓምዶች ስብስብ ያልተስተካከሉ መሆናቸውን ለማረጋገጥ, ከላይ ያለውን አገላለጽ በተለዋዋጭነት እንፈጥራለን. የረድፍ እና የኢንፕ ተለዋዋጮችን በመጠቀም እያንዳንዱን ዓምድ ለማስላት መጀመሪያ ተግባራትን እንገልፃቸው፡

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

አንዳንድ ዓምዶች ልዩ ናቸው፣ የመጀመሪያ እሴታቸው በተግባሩ መቆጠር የለበትም። በረድፍ[`numTrades] አምድ የመጀመሪያው መሆኑን ልንወስን እንችላለን - 0 ከያዘ እሴቱ መጀመሪያ ነው። Q የተመረጠ ተግባር አለው - ?[Boolean list;list1;list2] - ከዝርዝሩ 1 ወይም 2 እሴትን የሚመርጠው በመጀመሪያው ነጋሪ እሴት ላይ ባለው ሁኔታ፡-

// high -> ?[isFirst;inp`high;row[`high]|inp`high]
// @ - тоже обобщенное присваивание для случая когда индекс неглубокий
@[`aggExpression;specialCols;{[x;y]"?[isFirst;inp`",y,";",x,"]"};string specialCols];

እዚህ ጋር ከተግባሬ ጋር አንድ አጠቃላይ ስራ ጠርቻለሁ (በጥምዝ ቅንፎች ውስጥ ያለ መግለጫ)። የአሁኑን ዋጋ (የመጀመሪያውን ነጋሪ እሴት) እና ተጨማሪ ነጋሪ እሴት ይቀበላል, በ 4 ኛ ግቤት ውስጥ አልፋለሁ.

ተግባራቱ ለእነሱ አንድ አይነት ስለሆነ የባትሪ ድምጽ ማጉያዎችን ለየብቻ እንጨምር፡-

// volume -> row[`volume]+inp`volume
aggExpression[accumulatorCols]:{"row[`",x,"]+inp`",x } each string accumulatorCols;

ይህ በQ ደረጃዎች የተለመደ ተግባር ነው፣ ግን በአንድ ጊዜ የእሴቶችን ዝርዝር እየመደብኩ ነው። በመጨረሻም ዋናውን ተግባር እንፍጠር፡-

// ":",/: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),")]}";

በዚህ አገላለጽ፣ ከላይ የሰጠሁትን አገላለጽ የያዘ ተግባርን በተለዋዋጭነት ከአንድ ሕብረቁምፊ እፈጥራለሁ። ውጤቱም ይህን ይመስላል።

{[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])]}

የዓምድ ግምገማ ቅደም ተከተል የተገለበጠ ነው ምክንያቱም በQ ውስጥ የግምገማ ቅደም ተከተል ከቀኝ ወደ ግራ ነው።

አሁን ለስሌቶች አስፈላጊ የሆኑ ሁለት ዋና ተግባራት አሉን, ትንሽ መሠረተ ልማት ማከል ብቻ ያስፈልገናል እና አገልግሎቱ ዝግጁ ነው.

የመጨረሻ ደረጃዎች

ሁሉንም ስራ የሚሰሩ የAgg ተግባራትን ማዘመን እና ማዘመን አለብን። ግን አሁንም በደቂቃዎች ውስጥ ትክክለኛውን ሽግግር ማረጋገጥ እና የመደመር ኢንዴክሶችን ማስላት ያስፈልጋል። በመጀመሪያ ፣ የመግቢያ ተግባሩን እንገልፃለን-

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
 }

እንዲሁም የአሁኑን ደቂቃ የሚቀይረውን የጥቅልል ተግባር እንገልፃለን፡

roll:{[tm]
  if[currTime>tm; :init[]]; // если перевалили за полночь, то просто вызовем init
  rollCache,::offset _ rollColumns#tradeAgg; // обновим кэш – взять roll колонки из aggTable, обрезать, вставить в rollCache
  offset::count tradeAgg;
  currSyms::`u#`$();
 }

አዲስ ቁምፊዎችን ለመጨመር ተግባር ያስፈልገናል፡-

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

እና በመጨረሻም ፣ የተሻሻለው ተግባር (የዚህ ተግባር ለQ አገልግሎቶች ባህላዊ ስም) ፣ ይህም ውሂብ ለመጨመር በደንበኛው ይጠራል።

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]; // обновим агрегированную таблицу. Функция ? ищет индекс элементов списка справа в списке слева.
 };

ይኼው ነው. በገባው ቃል መሠረት ሙሉው የአገልግሎታችን ኮድ ጥቂት መስመሮች ብቻ አሉ።

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];
 };

ሙከራ

የአገልግሎቱን አፈጻጸም እንፈትሽ። ይህንን ለማድረግ በተለየ ሂደት ውስጥ እናስኬደው (ኮዱን በአገልግሎት.q ፋይል ውስጥ እናስቀምጠው) እና የመግቢያ ተግባሩን ይደውሉ፡

q service.q –p 5566

q)init[]

በሌላ ኮንሶል ውስጥ ሁለተኛውን የQ ሂደት ይጀምሩ እና ከመጀመሪያው ጋር ይገናኙ፡

h:hopen `:host:5566
h:hopen 5566 // если оба на одном хосте

በመጀመሪያ ፣ የምልክቶችን ዝርዝር - 10000 ቁርጥራጮች እንፍጠር እና የዘፈቀደ ሰንጠረዥ ለመፍጠር ተግባር እንጨምር። በሁለተኛው ኮንሶል ውስጥ፡-

syms:`IBM`AAPL`GOOG,-9997?`8
rnd:{[n;t] ([] sym:n?syms; time:t+asc n#til 25; price:n?10f; size:n?10)}

በሠንጠረዡ ውስጥ ለመፈለግ ቀላል ለማድረግ ሦስት እውነተኛ ምልክቶችን ወደ ዝርዝሩ ጨምሬያለሁ። የ rnd ተግባር n ረድፎች ያለው የዘፈቀደ ሰንጠረዥ ይፈጥራል፣ ሰዓቱም ከ t እስከ t +25 ሚሊሰከንድ ይለያያል።

አሁን ወደ አገልግሎቱ ውሂብ ለመላክ መሞከር ይችላሉ (የመጀመሪያዎቹን አስር ሰዓቶች ይጨምሩ)

{h (`upd;`trade;rnd[10000;x])} each `time$00:00 + til 60*10

በአገልግሎቱ ውስጥ ሠንጠረዡ እንደተዘመነ ማረጋገጥ ይችላሉ፡-

c 25 200
select from tradeAgg where sym=`AAPL
-20#select from tradeAgg where sym=`AAPL

ውጤት:

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

አገልግሎቱ በደቂቃ ምን ያህል ውሂብ እንደሚያስኬድ ለማወቅ አሁን የጭነት ሙከራን እናድርግ። የዝማኔ ክፍተቱን ወደ 25 ሚሊሰከንዶች እንዳዘጋጀን ላስታውስህ። በዚህ መሠረት ተጠቃሚዎች መረጃን እንዲጠይቁ ጊዜ ለመስጠት አገልግሎቱ (በአማካይ) ቢያንስ 20 ሚሊሰከንድ በአንድ ማሻሻያ ውስጥ መግጠም አለበት። በሁለተኛው ሂደት ውስጥ የሚከተለውን አስገባ:

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 ሁለት ደቂቃ ነው። በመጀመሪያ ለ1000 ረድፎች በየ25 ሚሊሰከንድ ለመሮጥ መሞከር ትችላለህ፡-

start 1000

በእኔ ሁኔታ ውጤቱ በአንድ ዝማኔ ወደ ሁለት ሚሊሰከንዶች አካባቢ ነው። ስለዚህ ወዲያውኑ የረድፎችን ቁጥር ወደ 10.000 እጨምራለሁ፡

start 10000

ውጤት:

min| 00:00:00.004
avg| 9.191458
med| 9f
max| 00:00:00.030

በድጋሚ, ምንም ልዩ ነገር የለም, ግን ይህ 24 ሚሊዮን መስመሮች በደቂቃ, 400 ሺህ በሰከንድ. ከ25 ሚሊሰከንዶች በላይ፣ ዝማኔው የቀነሰው 5 ጊዜ ብቻ ነው፣ ይህም ደቂቃው ሲቀየር ይመስላል። ወደ 100.000 እንጨምር፡-

start 100000

ውጤት:

min| 00:00:00.013
avg| 25.11083
med| 24f
max| 00:00:00.108
q)sum times
00:02:00.532

እንደሚመለከቱት ፣ አገልግሎቱ በጭንቅ መቋቋም ይችላል ፣ ሆኖም ግን በውሃ ላይ ለመቆየት ችሏል። እንዲህ ዓይነቱ የመረጃ መጠን (በ 240 ሚሊዮን ረድፎች በደቂቃ) እጅግ በጣም ትልቅ ነው ፣ በእንደዚህ ዓይነት ሁኔታዎች ፣ የአገልግሎቱን በርካታ ክሎኖች (ወይም በደርዘን የሚቆጠሩ ክሎኖች) ማስጀመር የተለመደ ነው ፣ እያንዳንዱም የቁምፊዎቹን ክፍል ብቻ ያስኬዳል። አሁንም ውጤቱ በዋናነት በመረጃ ማከማቻ ላይ ያተኮረ ለተተረጎመ ቋንቋ አስደናቂ ነው።

ከእያንዳንዱ ማሻሻያ መጠን ጋር ጊዜ ለምን ቀጥተኛ ያልሆነ ያድጋል የሚለው ጥያቄ ሊነሳ ይችላል። ምክንያቱ የማሽቆልቆሉ ተግባር በእውነቱ የC ተግባር ሲሆን ይህም ከ updateAgg የበለጠ ቀልጣፋ ነው። ከተወሰነ የዝማኔ መጠን (10.000 አካባቢ) ጀምሮ ፣ updateAgg ጣሪያው ላይ ይደርሳል እና ከዚያ የማስፈጸሚያ ጊዜ በዝማኔው መጠን ላይ የተመካ አይደለም። በቅድመ ደረጃ Q ምክንያት ነው አገልግሎቱ እንደዚህ ያሉ መጠኖችን ለመፍጨት የቻለው። ይህ ከትልቅ ውሂብ ጋር ሲሰራ ትክክለኛውን ስልተ ቀመር መምረጥ ምን ያህል አስፈላጊ እንደሆነ ያጎላል. ሌላው ነጥብ በማህደረ ትውስታ ውስጥ ያለው ትክክለኛ የውሂብ ማከማቻ ነው. ውሂቡ በአዕማዱ ካልተከማቸ ወይም በጊዜ ካልታዘዘ እንደ TLB cache miss - በአቀነባባሪው የአድራሻ መሸጎጫ ውስጥ የማህደረ ትውስታ ገጽ አድራሻ አለመኖሩን እናውቀዋለን። አድራሻ መፈለግ ካልተሳካ 30 ጊዜ ያህል ይረዝማል እና መረጃው ከተበታተነ አገልግሎቱን ብዙ ጊዜ ሊያዘገየው ይችላል።

መደምደሚያ

በዚህ ጽሁፍ ውስጥ KDB+ እና Q ዳታቤዝ ትልቅ መረጃን ለማከማቸት እና በቀላሉ በተመረጠው መንገድ ለማግኘት ብቻ ሳይሆን በመቶ ሚሊዮኖች የሚቆጠር ረድፎች/ጊጋባይት ዳታ በ ውስጥ እንኳን መፈጨት የሚችሉ የመረጃ ማቀነባበሪያ አገልግሎቶችን ለመፍጠር ምቹ መሆናቸውን አሳይቻለሁ። አንድ ነጠላ Q ሂደት . የQ ቋንቋው በራሱ በቬክተር ተፈጥሮው፣ አብሮ በተሰራው የSQL ዘዬ አስተርጓሚ እና በጣም የተሳካ የቤተ-መጻህፍት ተግባራት ስብስብ ምክንያት ከመረጃ ማቀናበሪያ ጋር የተያያዙ ስልተ ቀመሮችን እጅግ በጣም አጭር እና ቀልጣፋ ተግባራዊ ለማድረግ ያስችላል።

ከዚህ በላይ ያለው Q ማድረግ የሚችለው አካል ብቻ እንደሆነ አስተውያለሁ፣ ሌሎች ልዩ ባህሪያትም አሉት። ለምሳሌ፣ እጅግ በጣም ቀላል የሆነ የአይፒሲ ፕሮቶኮል በግለሰብ Q ሂደቶች መካከል ያለውን ድንበር የሚሰርዝ እና በመቶዎች የሚቆጠሩ ሂደቶችን ወደ አንድ አውታረ መረብ እንዲያዋህዱ የሚያስችልዎት፣ ይህም በተለያዩ የአለም ክፍሎች በሚገኙ በደርዘን የሚቆጠሩ አገልጋዮች ላይ ይገኛል።

ምንጭ: hab.com

አስተያየት ያክሉ