තත්‍ය කාලීන සේවාවක උදාහරණය භාවිතා කරමින් Q සහ KDB+ භාෂාවේ විශේෂාංග

KDB+ පදනම, Q ක්‍රමලේඛන භාෂාව යනු කුමක්ද, ඒවායේ ශක්තීන් සහ දුර්වලතා මොනවාද යන්න පිළිබඳව මගේ පෙරදී ඔබට කියවිය හැක. ලිපියයි සහ හැඳින්වීමෙහි කෙටියෙන්. ලිපියෙහි, අපි Q මත සේවාවක් ක්‍රියාත්මක කරනු ඇති අතර එය පැමිණෙන දත්ත ප්‍රවාහය ක්‍රියාත්මක කරන අතර සෑම මිනිත්තුවකම “තත්‍ය කාල” ප්‍රකාරයේදී විවිධ එකතු කිරීමේ කාර්යයන් ගණනය කරනු ඇත (එනම්, දත්තවල ඊළඟ කොටසට පෙර සියල්ල ගණනය කිරීමට එයට කාලය ඇත). Q හි ප්‍රධාන ලක්ෂණය වන්නේ එය ඔබට තනි වස්තූන් සමඟ නොව, ඒවායේ අරා, අරාවන් සහ අනෙකුත් සංකීර්ණ වස්තූන් සමඟ ක්‍රියා කිරීමට ඉඩ සලසන දෛශික භාෂාවක් වීමයි. Q සහ එහි ඥාතීන් වන K, J, APL වැනි භාෂා ඔවුන්ගේ කෙටිකතාව සඳහා ප්‍රසිද්ධය. බොහෝ විට, Java වැනි හුරුපුරුදු භාෂාවකින් කේත තිර කිහිපයක් ගන්නා වැඩසටහනක් ඒවායේ පේළි කිහිපයකින් ලිවිය හැකිය. මෙම ලිපියෙන් මට පෙන්වීමට අවශ්‍ය වන්නේ මෙයයි.

තත්‍ය කාලීන සේවාවක උදාහරණය භාවිතා කරමින් Q සහ KDB+ භාෂාවේ විශේෂාංග

හැඳින්වීම

KDB+ යනු ඉතා විශාල දත්ත ප්‍රමාණයක් කෙරෙහි අවධානය යොමු කරන ලද තීරු දත්ත සමුදායක් වන අතර, එය නිශ්චිත ආකාරයකින් (ප්‍රධාන වශයෙන් කාලය අනුව) ඇණවුම් කර ඇත. එය මූලික වශයෙන් මූල්ය ආයතනවල භාවිතා වේ - බැංකු, ආයෝජන අරමුදල්, රක්ෂණ සමාගම්. Q භාෂාව යනු KDB+ හි අභ්‍යන්තර භාෂාව වන අතර එය ඔබට මෙම දත්ත සමඟ ඵලදායී ලෙස වැඩ කිරීමට ඉඩ සලසයි. Q දෘෂ්ටිවාදය කෙටිකතාව සහ කාර්යක්ෂමතාව වන අතර පැහැදිලිකම කැප කරයි. දෛශික භාෂාව ඕනෑම අවස්ථාවක තේරුම් ගැනීමට අපහසු වනු ඇති අතර, පටිගත කිරීමේ සංක්ෂිප්තභාවය සහ පොහොසත්කම මඟින් වැඩසටහනේ විශාල කොටසක් එක තිරයක දැකීමට ඔබට ඉඩ සලසයි, එය අවසානයේ එය තේරුම් ගැනීමට පහසු කරයි.

මෙම ලිපියෙන් අපි Q හි සම්පූර්ණ වැඩසටහනක් ක්‍රියාත්මක කරන අතර ඔබට එය උත්සාහ කිරීමට අවශ්‍ය විය හැකිය. මෙය සිදු කිරීම සඳහා, ඔබට සත්‍ය Q අවශ්‍ය වනු ඇත. ඔබට නොමිලේ 32-bit අනුවාදය kx සමාගමේ වෙබ් අඩවියෙන් බාගත හැකිය - www.kx.com. එහිදී, ඔබ උනන්දුවක් දක්වන්නේ නම්, ඔබට Q, පොත පිළිබඳ විමර්ශන තොරතුරු සොයාගත හැකිය Q Mortals සඳහා සහ මෙම මාතෘකාව පිළිබඳ විවිධ ලිපි.

ගැටලුව ප්රකාශ කිරීම

සෑම මිලි තත්පර 25 කට වරක් දත්ත සහිත වගුවක් යවන මූලාශ්‍රයක් තිබේ. KDB+ මූලික වශයෙන් මුල්‍යකරණයේදී භාවිතා වන බැවින්, මෙය පහත තීරු ඇති ගණුදෙණු (වෙළඳාම) වගුවක් බව අපි උපකල්පනය කරමු: කාලය (කාලය මිලි තත්පර වලින්), sym (කොටස් හුවමාරුවේ සමාගම් තනතුර - IBM ආයතනය, AAPL,...), මිල (කොටස් මිලදී ගත් මිල), ප්රමාණය (ගනුදෙනුවෙහි ප්රමාණය). මිලි තත්පර 25 ක පරතරය අත්තනෝමතික ය, ඉතා කුඩා නොවේ සහ දිගු නොවේ. එහි පැමිණීමෙන් අදහස් වන්නේ දත්ත දැනටමත් බෆර කර ඇති සේවාව වෙත පැමිණෙන බවයි. වත්මන් භාරය අනුව ගතික බෆරින් ඇතුළුව සේවා පැත්තේ බෆරින් ක්‍රියාත්මක කිරීම පහසු වනු ඇත, නමුත් සරල බව සඳහා, අපි ස්ථාවර පරතරයක් කෙරෙහි අවධානය යොමු කරමු.

සේවාව සෑම මිනිත්තුවකම සිම් තීරුවෙන් ලැබෙන සෑම සංකේතයක් සඳහාම එකතු කිරීමේ ශ්‍රිත මාලාවක් ගණන් කළ යුතුය - උපරිම මිල, සාමාන්‍ය මිල, එකතුවේ ප්‍රමාණය, ආදිය. ප්රයෝජනවත් තොරතුරු. සරල බව සඳහා, සියලුම කාර්යයන් වර්ධක ලෙස ගණනය කළ හැකි යැයි අපි උපකල්පනය කරමු, i.e. නව අගයක් ලබා ගැනීම සඳහා, අංක දෙකක් දැන ගැනීම ප්රමාණවත්ය - පැරණි සහ ලැබෙන අගයන්. උදාහරණයක් ලෙස, max, average, sum යන ශ්‍රිතවලට මෙම ගුණය ඇත, නමුත් මධ්‍ය ශ්‍රිතයට නැත.

එන දත්ත ප්‍රවාහය කාලය නියම කර ඇති බව ද අපි උපකල්පනය කරමු. මේකෙන් අපිට අන්තිම මොහොතත් එක්ක විතරක් වැඩ කරන්න අවස්ථාව ලැබෙනවා. ප්‍රායෝගිකව, සමහර යාවත්කාලීන කිරීම් ප්‍රමාද වුවහොත් වත්මන් සහ පෙර මිනිත්තු සමඟ වැඩ කිරීමට හැකිවීම ප්‍රමාණවත් වේ. සරල බව සඳහා, අපි මෙම නඩුව සලකා බලන්නේ නැත.

එකතු කිරීමේ කාර්යයන්

අවශ්‍ය එකතු කිරීමේ කාර්යයන් පහත දක්වා ඇත. සේවාවේ බර වැඩි කිරීම සඳහා මම හැකි තරම් ඒවා ගත්තා:

  • ඉහළ - උපරිම මිල - විනාඩියකට උපරිම මිල.
  • අඩු - අවම මිල - විනාඩියකට අවම මිල.
  • පළමු මිල - පළමු මිල - විනාඩියකට පළමු මිල.
  • අවසාන මිල - අවසාන මිල - විනාඩියකට අවසාන මිල.
  • පළමු ප්‍රමාණය - පළමු ප්‍රමාණය - මිනිත්තුවකට පළමු වෙළඳ ප්‍රමාණය.
  • lastSize - අවසාන ප්‍රමාණය - මිනිත්තුවකින් අවසන් වෙළඳ ප්‍රමාණය.
  • numTrades - ගණන් i - විනාඩියකට ගනුදෙනු ගණන.
  • පරිමාව - එකතුව ප්රමාණය - විනාඩියකට වෙළඳ ප්රමාණ එකතුව.
  • pvolume - එකතුව මිල - විනාඩියකට මිල එකතුව, avgPrice සඳහා අවශ්‍ය වේ.
  • - එකතුව පිරිවැටුම් මිල* ප්රමාණය - මිනිත්තුවකට ගනුදෙනුවල මුළු පරිමාව.
  • avgPrice - pvolume%numTrades - විනාඩියකට සාමාන්‍ය මිල.
  • avgSize - volume%numTrades - මිනිත්තුවකට සාමාන්‍ය වෙළඳ ප්‍රමාණය.
  • vwap - පිරිවැටුම% පරිමාව - ගනුදෙනු ප්‍රමාණය අනුව මිනිත්තුවකට සාමාන්‍ය මිල.
  • cumVolume - එකතුව පරිමාව - මුළු කාලය පුරාවටම සමුච්චිත ගනුදෙනු ප්‍රමාණය.

අපි වහාම එක් පැහැදිලි නොවන කරුණක් ගැන සාකච්ඡා කරමු - මෙම තීරු පළමු වරට ආරම්භ කරන්නේ කෙසේද සහ ඊළඟ සෑම මිනිත්තුවක් සඳහාම. පළමු මිල වර්ගයේ සමහර තීරු සෑම අවස්ථාවකම ශුන්‍ය කිරීමට ආරම්භ කළ යුතුය; ඒවායේ අගය නිර්වචනය කර නොමැත. අනෙකුත් වෙළුම් වර්ග සෑම විටම 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 объяснен ниже

මම පහසුව සඳහා ශබ්ද කෝෂයට sym සහ වේලාව එක් කළෙමි, දැන් initWith යනු අවසන් එකතු කළ වගුවේ සිට සකස් කළ රේඛාවකි, එහිදී නිවැරදි sym සහ වේලාව සැකසීමට ඉතිරිව ඇත. මේසයකට නව පේළි එකතු කිරීමට ඔබට එය භාවිතා කළ හැක.

එකතු කිරීමේ කාර්යයක් නිර්මාණය කිරීමේදී අපට aggCols අවශ්‍ය වේ. Q හි ප්‍රකාශන (දකුණේ සිට වමට) ඇගයීමට ලක් කරන අනුපිළිවෙල හේතුවෙන් ලැයිස්තුව ප්‍රතිලෝම කළ යුතුය. සමහර තීරු පෙර ඒවා මත රඳා පවතින බැවින්, ගණනය කිරීම ඉහළ සිට cumVolume දක්වා යාම සහතික කිරීම ඉලක්කයයි.

පෙර මිනිත්තුවේ සිට නව මිනිත්තුවකට පිටපත් කළ යුතු තීරු, පහසුව සඳහා sym තීරුව එකතු කරනු ලැබේ:

rollColumns:`sym`cumVolume;

දැන් අපි යාවත්කාලීන කළ යුතු ආකාරය අනුව තීරු කණ්ඩායම් වලට බෙදා ගනිමු. වර්ග තුනක් වෙන්කර හඳුනාගත හැකිය:

  1. ඇකියුමුලේටර් (පරිමාව, පිරිවැටුම, ..) - අපි පෙරට එන අගය එකතු කළ යුතුය.
  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 ප්‍රකාශනයක් සහිත තන්තුවක් eval ශ්‍රිතයට යැවිය හැකි අගයක් බවට පත් කරන අතර එය තේරීමේ ශ්‍රිතයේ අවශ්‍ය වේ. පෙර ක්‍රියාවලි යනු තෝරාගත් ශ්‍රිතයේ ප්‍රක්ෂේපණයක් (එනම්, අර්ධ වශයෙන් අර්ථ දක්වා ඇති තර්ක සහිත ශ්‍රිතයක්) ලෙස අර්ථ දක්වා ඇති බව සලකන්න, එක් තර්කයක් (වගුව) අතුරුදහන් වේ. අපි වගුවකට පෙර සැකසුම් යෙදුවහොත්, අපට සම්පීඩිත වගුවක් ලැබේ.

දෙවන අදියර වන්නේ එකතු කළ වගුව යාවත්කාලීන කිරීමයි. අපි මුලින්ම algorithm එක 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 හි, ලූප වෙනුවට සිතියම්/අඩු කිරීමේ ශ්‍රිත භාවිතා කිරීම සාමාන්‍ය දෙයකි. නමුත් 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;…)];

අවාසනාවන්ත ලෙස, වගුවකට පැවරීම සඳහා ඔබට තීරු නොව පේළි ලැයිස්තුවක් අවශ්‍ය වන අතර, ඔබ flip ශ්‍රිතය භාවිතයෙන් අනුකෘතිය (පේළි ලැයිස්තුවට තීරු ලැයිස්තුව) මාරු කළ යුතුය. මෙය විශාල වගුවක් සඳහා මිල අධික වේ, ඒ නිසා ඒ වෙනුවට අපි සිතියම් ශ්‍රිතය භාවිතා කරමින් (එය අපෝස්ට්‍රොෆි ලෙස පෙනෙන) එක් එක් තීරුවට වෙන වෙනම සාමාන්‍යකරණය කළ පැවරුමක් යොදන්නෙමු:

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

අපි නැවතත් ශ්‍රිත ප්‍රක්ෂේපණය භාවිතා කරමු. Q හි, ලැයිස්තුවක් නිර්මාණය කිරීම ද ශ්‍රිතයක් වන අතර ලැයිස්තු ලැයිස්තුවක් ලබා ගැනීම සඳහා අපට එක් එක්(සිතියම) ශ්‍රිතය භාවිතයෙන් එය ඇමතීමට හැකි බව සලකන්න.

ගණනය කළ තීරු කට්ටලය ස්ථාවර නොවන බව සහතික කිරීම සඳහා, අපි ඉහත ප්රකාශනය ගතිකව නිර්මාණය කරමු. එකතු කළ සහ ආදාන දත්ත වෙත යොමු කිරීම සඳහා පේළිය සහ inp විචල්‍යයන් භාවිතා කරමින්, එක් එක් තීරු ගණනය කිරීම සඳහා ශ්‍රිත පළමුව නිර්වචනය කරමු:

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 හට තෝරාගත් ශ්‍රිතයක් ඇත - ?[බූලියන් ලැයිස්තුව;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 ශ්‍රිතය නිර්වචනය කරමු:

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

පරීක්ෂා කිරීම

සේවාවේ කාර්ය සාධනය පරීක්ෂා කරමු. මෙය සිදු කිරීම සඳහා, අපි එය වෙනම ක්‍රියාවලියකින් ධාවනය කරමු (කේතය service.q ගොනුවට දමන්න) සහ init ශ්‍රිතය අමතන්න:

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 milli seconds දක්වා වෙනස් වේ.

දැන් ඔබට සේවාව වෙත දත්ත යැවීමට උත්සාහ කළ හැකිය (පළමු පැය දහය එක් කරන්න):

{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 ක්) අතිශයින් විශාල ය; එවැනි අවස්ථාවන්හිදී, සේවාවේ ක්ලෝන කිහිපයක් (හෝ ක්ලෝන දුසිම් ගණනක්) දියත් කිරීම සාමාන්‍ය දෙයකි, ඒ සෑම එකක්ම අක්ෂරවලින් කොටසක් පමණක් සකසයි. තවමත්, දත්ත ගබඩා කිරීම මත මූලිකව අවධානය යොමු කරන අර්ථකථන භාෂාවක් සඳහා ප්‍රතිඵලය සිත් ඇදගන්නා සුළුය.

එක් එක් යාවත්කාලීනයේ ප්‍රමාණය සමඟ කාලය රේඛීය නොවන ලෙස වර්ධනය වන්නේ මන්ද යන ප්‍රශ්නය මතු විය හැක. හේතුව නම්, shrink ශ්‍රිතය ඇත්ත වශයෙන්ම C ශ්‍රිතයක් වන අතර, එය updateAgg වලට වඩා බොහෝ කාර්යක්ෂම වේ. නිශ්චිත යාවත්කාලීන ප්‍රමාණයකින් (10.000 පමණ) පටන් ගෙන, updateAgg එහි සිවිලිමට ළඟා වන අතර පසුව එහි ක්‍රියාත්මක කාලය යාවත්කාලීන ප්‍රමාණය මත රඳා නොපවතී. එවැනි දත්ත පරිමාවන් ජීර්ණය කිරීමට සේවාවට හැකි වන්නේ Q ප්‍රාථමික පියවර නිසාය. විශාල දත්ත සමඟ වැඩ කිරීමේදී නිවැරදි ඇල්ගොරිතම තෝරා ගැනීම කොතරම් වැදගත්ද යන්න මෙය අවධාරණය කරයි. තවත් කරුණක් වන්නේ මතකයේ දත්ත නිවැරදිව ගබඩා කිරීමයි. දත්ත තීරු ආකාරයෙන් ගබඩා කර නොමැති නම් හෝ නියමිත වේලාවට ඇණවුම් නොකළේ නම්, TLB හැඹිලි මග හැරීමක් වැනි දෙයක් අපට හුරුපුරුදු වනු ඇත - ප්‍රොසෙසර ලිපින හැඹිලියේ මතක පිටු ලිපිනයක් නොමැති වීම. ලිපිනයක් සෙවීම අසාර්ථක වුවහොත් 30 ගුණයක් පමණ වැඩි කාලයක් ගත වන අතර, දත්ත විසිරී ඇත්නම්, එය සේවාව කිහිප වතාවක් මන්දගාමී විය හැක.

නිගමනය

මෙම ලිපියෙන්, KDB+ සහ Q දත්ත සමුදාය විශාල දත්ත ගබඩා කිරීමට සහ තේරීම හරහා පහසුවෙන් ප්‍රවේශ වීමට පමණක් නොව, මිලියන සිය ගණනක පේළි / ගිගාබයිට් දත්ත ජීර්ණය කළ හැකි දත්ත සැකසුම් සේවා නිර්මාණය කිරීමට ද සුදුසු බව මම පෙන්වා දුන්නෙමි. එක් තනි Q ක්රියාවලිය. Q භාෂාව විසින්ම එහි දෛශික ස්වභාවය, ගොඩනඟන ලද SQL උපභාෂා පරිවර්තකය සහ ඉතා සාර්ථක පුස්තකාල ශ්‍රිත සමූහයක් හේතුවෙන් දත්ත සැකසීමට අදාළ ඇල්ගොරිතම අතිශයින් සංක්ෂිප්ත හා කාර්යක්ෂමව ක්‍රියාත්මක කිරීමට ඉඩ සලසයි.

ඉහත සඳහන් කර ඇත්තේ Q කළ හැකි දේවල කොටසක් පමණක් බව මම සටහන් කරමි, එයට වෙනත් සුවිශේෂී ලක්ෂණ ද ඇත. නිදසුනක් ලෙස, තනි Q ක්‍රියාවලි අතර මායිම මකා දමන අතිශය සරල IPC ප්‍රොටෝකෝලයක් සහ ලෝකයේ විවිධ ප්‍රදේශවල සේවාදායකයන් දුසිම් ගණනක ස්ථානගත කළ හැකි මෙම ක්‍රියාවලි සිය ගණනක් තනි ජාලයකට ඒකාබද්ධ කිරීමට ඔබට ඉඩ සලසයි.

මූලාශ්රය: www.habr.com

අදහස් එක් කරන්න