-- Synthetic Bonds For QUIK
-- v1.0
-- (c) Aphelion, 2021
--[[
Для корректной работы скрипта необходимо в настройках заказать получение данных по классам:
SPBFUT (FORTS), TQBR (МБ ФР: T+ Акции и Др) и FQBR (МБ ФР: T+ Ин.Акции и Др)
Фильтры по инструментам и параметрам должны быть выключены.
]]--
local requiredYield = 8 -- Показывать синт. облигации с доходностью не ниже X % годовых
local requiredDaysToMaturity = 30 -- Показывать синт. облигации со сроком жизни не меньше X дней
local symbols = {
-- Код фьючерса = {Класс Акции, Код Акции}
AF = {'TQBR', 'AFLT'},
AL = {'TQBR', 'ALRS'},
CH = {'TQBR', 'CHMF'},
FS = {'TQBR', 'FEES'},
GZ = {'TQBR', 'GAZP'},
GK = {'TQBR', 'GMKN'},
HY = {'TQBR', 'HYDR'},
LK = {'TQBR', 'LKOH'},
MN = {'TQBR', 'MGNT'},
ME = {'TQBR', 'MOEX'},
MT = {'TQBR', 'MTSS'},
NM = {'TQBR', 'NLMK'},
NK = {'TQBR', 'NVTK'},
RN = {'TQBR', 'ROSN'},
RT = {'TQBR', 'RTKM'},
SP = {'TQBR', 'SBERP'},
SR = {'TQBR', 'SBER'},
SG = {'TQBR', 'SNGSP'},
SN = {'TQBR', 'SNGS'},
TT = {'TQBR', 'TATN'},
TN = {'TQBR', 'TRNFP'},
VB = {'TQBR', 'VTBR'},
MG = {'TQBR', 'MAGN'},
PZ = {'TQBR', 'PLZL'},
YN = {'TQBR', 'YNDX'},
AK = {'TQBR', 'AFKS'},
IR = {'TQBR', 'IRAO'},
PO = {'TQBR', 'POLY'},
TI = {'TQBR', 'TCSG'},
FV = {'TQBR', 'FIVE'},
ML = {'TQBR', 'MAIL'},
OZ = {'TQBR', 'OZON'},
PI = {'TQBR', 'PIKK'},
BA = {'FQBR', 'BABA-RM'},
BI = {'FQBR', 'BIDU-RM'}
}
local running = true
local bondsTable = nil
local index = {}
function main()
local result = indexFutures()
if not result then
message('SBonds: Failed To Index Futures', 3)
do return end
end
createSyntheticBondsTable()
while running do
calculateYields()
sleep(1000)
end
end
function indexFutures()
local SPBFUT = getClassSecurities('SPBFUT')
local matches = string.gmatch(SPBFUT, '([^,]+)')
local count = 0
for futures in matches do
local futuresCode = string.sub(futures, 1, 2)
if symbols[futuresCode] ~= nil then
if index[futuresCode] == nil then
index[futuresCode] = {}
count = count + 1
end
index[futuresCode][#index[futuresCode] + 1] = {
code = futures,
scale = getSecurityInfo('SPBFUT', futures).scale,
size = getSecurityInfo('SPBFUT', futures).lot_size,
expiration = string.format('%.0f', getParamEx('SPBFUT', futures, 'MAT_DATE').param_value)
}
end
end
if count == 0 then
return false
end
return true
end
function calculateYields()
local tableIndex = {}
local rows, _ = GetTableSize(bondsTable)
for i = 1, rows do
local cell = GetCell(bondsTable, i, 0)
tableIndex[cell.image] = i
end
for futuresCode, items in pairs(index) do
if symbols[futuresCode] ~= nil then
for _, item in pairs(items) do
local class = symbols[futuresCode][1]
local stock = symbols[futuresCode][2]
if isTrading(class, stock) and isTrading('SPBFUT', item.code) then
local futuresBid = tonumber(getParamEx('SPBFUT', item.code, 'BID').param_value)
local stockAsk = tonumber(getParamEx(class, stock, 'OFFER').param_value)
if futuresBid > 0 and stockAsk > 0 then
local term = timeToMaturity(item.expiration)
local yield = (futuresBid - stockAsk * item.size) / term / (stockAsk * item.size) * 100
if term >= requiredDaysToMaturity / 365 and yield >= requiredYield then
local bond = stock .. ' x ' .. item.code
local row = tableIndex[bond]
if row == nil then
row = InsertRow(bondsTable, -1)
SetCell(bondsTable, row, 0, bond)
end
local stockScale = getSecurityInfo(class, stock).scale
SetCell(bondsTable, row, 1, string.format('%.' .. stockScale .. 'f', stockAsk), stockAsk)
SetCell(bondsTable, row, 2, string.format('%.' .. item.scale .. 'f', futuresBid), futuresBid)
SetCell(bondsTable, row, 3, string.format('%.0f', term * 365), term * 365)
SetCell(bondsTable, row, 4, string.format('%.2f', yield) .. '%', yield)
tableIndex[bond] = nil
end
end
end
end
end
end
for _, row in pairs(tableIndex) do
DeleteRow(bondsTable, row)
end
end
function OnStop()
DestroyTable(bondsTable)
running = false
return 0
end
function createSyntheticBondsTable()
bondsTable = AllocTable()
AddColumn(bondsTable, 0, 'Bond', true, QTABLE_STRING_TYPE, 17)
AddColumn(bondsTable, 1, 'Stock Ask', true, QTABLE_DOUBLE_TYPE, 12)
AddColumn(bondsTable, 2, 'Futures Bid', true, QTABLE_DOUBLE_TYPE, 12)
AddColumn(bondsTable, 3, 'Days', true, QTABLE_DOUBLE_TYPE, 10)
AddColumn(bondsTable, 4, 'Yield', true, QTABLE_DOUBLE_TYPE, 12)
CreateWindow(bondsTable)
SetWindowCaption(bondsTable, 'Synthetic Bonds')
SetWindowPos(bondsTable, 0, 0, 460, 230)
SetTableNotificationCallback(bondsTable, tableNotificationCallback)
end
function tableNotificationCallback(sender, event, param1, param2)
if sender == bondsTable and event == QTABLE_CLOSE then
running = false
end
end
function isTrading(class, security)
local status = tonumber(getParamEx(class, security, class == 'SPBFUT' and 'STATUS' or 'TRADINGSTATUS').param_value)
return status == 1
end
function timeToMaturity(string)
local year = string.sub(string, 1, 4)
local month = string.sub(string, 5, 6)
local day = string.sub(string, 7, 8)
local date = os.time({
day = tonumber(day),
year = tonumber(year),
month = tonumber(month),
hour = 18,
min = 45,
sec = 0
})
local time = os.difftime(date, os.time()) / (365 * 24 * 60 * 60)
return time
end