Сегодня на тестовом контуре в таблице depo_limit получил такое:
limit_kind = 20250116
Это что? Ясно что дата. Но с какой такой... Я не слышал о новом режиме торгов в виде даты. Есть Т0, Т+1, Т+2, TOD, TOM и новое Т365, отображаемое в терминале Тх.
Это результат перехода тестового сервера на ведение позиций по календарным датам. limit_kind = 20250116 это тоже, что и limit_kind = 0.
Сообщение в терминале увидел. Вот оно, чтобы было понятно остальным:
Здравствуйте.С 16 января сервер QUIK Junior переведен на современную схему ведения позиций - по календарным датам. Если ранее каждой позиции соответствовал код расчетов (T0, T1, T2), то теперь - конкретная дата. Например, если сегодня 16.01.2025, значит код расчета T0 соответствует дате расчета 16.01.2025, код T1 - 17.01.2025 и так далее. Ожидается, что в будущем на эту схему перейдет большинство брокерских компаний. Кроме того, теперь расчеты по всем инструментам на сервере происходят по схеме T+1, что соответствует режиму реальных торгов на Московской Бирже. В связи с этим в таблицах с позициями по инструментам и деньгам следует заменить параметр "Срок расчетов" на "Дата расчетов". В таблице "Состояние счета" вместо кодов расчета теперь подставляется конкретная дата.
Правда это, конечно, несколько неожиданно, не говоря уже о каких-то формальных документах. Спрашивается а как теперь для инструментов, торгуемых в разных режимах определять позицию. До этого можно было найти максимальный limit_kind и по нему фильтровать записи. И он не изменялся. Теперь же схема работы становится печальной - каждый день этот параметр будет изменяться, т.е. необходимо заново его обновлять, и это если брокеры не придумают чего-то еще. На сайте биржи да и в транслируемых данных есть показатель режима торгов для инструмента. Никаких расчётных величин нет. Зачем это, какую проблему решает это нововведение? Представляю, что ждет работающие скрипты при переходе брокеров на этот режим.
как будут работать методы, где есть входной параметр limit_kind?
При работе в схеме с календарными датами в limit_kind необходимо передать дату расчетов, на которую необходимо получить позицию (NUMBER в формате YYYYMMDD).
Цитата
как теперь для инструментов, торгуемых в разных режимах определять позицию
Предлагаем воспользоваться следующими функциями - функции могут принимать на ввод в параметре limit_kind конкретную дату расчетов, либо значение "Tx". Если передана дата, функция вернет позицию на указанную дату, если такая имеется - в противном случае возвращается позиция, установленная на ближайшую предшествующую указанной дату, либо 0, если позицию по такому условию не удалось найти. Если передано значение "Tx", функция вернет плановую позицию (с максимальной датой рачетов).
Скрытый текст
Код
function getMoneyEx_byDate(firmid, client_code, tag, currcode, limit_kind)
res = getMoneyEx(firmid, client_code, tag, currcode, limit_kind)
if not res then
res = 0
n = getNumberOf("money_limits")
lk = 0
function fn(m)
if limit_kind == "Tx" then
qry = (m.firmid == firmid and m.client_code == client_code and m.tag == tag and m.currcode == currcode)
else
qry = (m.firmid == firmid and m.client_code == client_code and m.tag == tag and m.currcode == currcode and m.limit_kind < limit_kind)
end
if qry then
if m.limit_kind > lk then
lk = m.limit_kind
res = m
end
return true
else
return false
end
end
SearchItems("money_limits", 0, n - 1, fn)
end
return res
end
function getDepoEx_byDate(firmid, client_code, sec_code, trdaccid, limit_kind)
res = getDepoEx(firmid, client_code, sec_code, trdaccid, limit_kind)
if not res then
res = 0
n = getNumberOf("depo_limits")
lk = 0
function fn(d)
if limit_kind == "Tx" then
qry = (d.firmid == firmid and d.client_code == client_code and d.sec_code == sec_code and d.trdaccid == trdaccid)
else
qry = (d.firmid == firmid and d.client_code == client_code and d.sec_code == sec_code and d.trdaccid == trdaccid and d.limit_kind < limit_kind)
end
if qry then
if d.limit_kind > lk then
lk = d.limit_kind
res = d
end
return true
else
return false
end
end
SearchItems("depo_limits", 0, n - 1, fn)
end
return res
end
Схема ведения позиций по календарным датам поддерживается Рабочим местом QUIK версии 10.2.0 и выше, соответственно, приведенный код будет работать начиная с этой версии. При подключении к серверу QUIK-Junior рекомендуем использовать терминал актуальной версии 11.4.1.
Anton Belonogov написал: Предлагаем воспользоваться следующими функциями - функции могут принимать на ввод в параметре limit_kind конкретную дату расчетов, либо значение "Tx".
А справку не нужно поправить? В ней limit_kind указан как NUMBER. Какой еще "Tx"? getBuySellInfoEx теперь тоже будет возвращать не пойми что? Тоже поправьте в справке.
А в целом кто так делает? По сути limit_kind это теперь новый параметр содержащий не вид лимита а дату, соответственно никакой обратной совместимости, это приведет к полной неработоспособности всех ранее написанных программ.
Мы привели пример функций для работы с позициями в календарных датах, основанных на getMoneyEx и getDepoEx. Исходные функции (getMoneyEx и getDepoEx), а также другие функции QLua остались без изменений.
При использовании схемы с календарными датами срок расчетов портфеля соответствует плановой позиции - в этом случае в getPortfolioInfoEx передается limit_kind = 365.
В данной схеме обнаружена она проблема, затрудняющая её использование. В таблице money_limit поле limit_kind может изменится в процессе работы. Например, считываем данные по инструменту, по которому нет позиции, получаем текущую дату. Открываем позицию и в поле limit_kind уже дата +1. Не очень понятно как предлагается работать, если данные параметр будут постоянно изменяться. Если только просто не прибавлять всегда 1, т.к. заявляется, что вернётся ближайшее значение.
Хотя нет, это ваша реализация так работает. Прямой фильтр по money_limits требует точного соответствия. Это, конечно, просто великолепно вносить изменения ломающие обратную совместимость в ключевые механизмы по работе с инструментами.
Речь о поведении данных в таблице money_limits поле limit_kind. Вы предложили реализацию через перебор данных и поиск максимального. Такой подход возможен, да. Но возникает вопрос - почему вносимые изменения потребовали использования такого подхода.
limit_kind - это режим торгов инструмента, он задан в его спецификации и не изменится в течении торговой сессии. Наоборот, это достаточно постоянный параметр. То что дата расчётов в связи с выходными может быть дальше, не означает, что необходимо теперь переходить на хранение даты. То что в течении торговой сессии произошли с сделки с инструментом, не означает, что его режим торгов изменится. Он как был Т+1, так и остался. Просто позиция будет той же завтра, если нет сделок, не более. Сейчас же происходит замечательная вещь - нет сделок, получаем режим торгов Т+0 (текущая дата), совершили сделки, получаем Т+1 (следующая дата). Т.е. можно было бы добавить дату расчётов в таблицу, чтобы знать когда произойдут взаиморасчёты, но базовый фильтр limit_kind почему был изменён - совершенно не ясно. Это не дата расчётов. С этим можно было жить как раньше, если бы он не изменялся в течении торгов, но и это не так. Теперь действительно необходимо постоянно сканировать на поиск максимального значения.
Anton Belonogov написал: limit_kind - это параметр позиции на сервере QUIK, а не параметр инструмента в Торговой системе.Торговые режимы остаются прежними, изменяется только ведение позиций на сервере, что позволяет для каждой позиции видеть даты предстоящих расчетов.
Почему бы не оставить в покое limit_kind (как было)? В чем была проблема завести новое поле "Дату предстоящих торгов"?
В зависимости от установленной на сервере схемы позиции учитываются либо по срокам расчетов (T0, T1), либо по датам. Это взаимоисключающие параметры, сервер транслирует на терминалы только один из них, поэтому смысла в дополнительном параметре для QLua нет.
В зависимости от установленной на сервере схемы позиции учитываются либо по срокам расчетов (T0, T1), либо по датам. Это взаимоисключающие параметры, сервер транслирует на терминалы только один из них, поэтому смысла в дополнительном параметре для QLua нет.
А как писать код, чтобы он работал на разных брокерах? У меня 3 брокера и если они будут использовать разные схемы мне писать обработку для каждого брокера??? За что вы так не любите своих пользователей???
Было бы прекрасно, если бы вы привели пример кода, как посмотреть открытые позиции по каждому инструменту на дату Т0, Т1, Т2 с учетом того, что брокеры могут использовать различные схемы.
Андрей написал: как узнать какую схему использует какой брокер?
Это можно узнать по limit_kind на позициях: если включена схема ведения позиций по календарным датам, limit_kind будет иметь вид YYYYMMDD, в противном случае, при использовании старой схемы - 0, 1, 2, 365.
Цитата
Андрей написал: У меня 3 брокера и если они будут использовать разные схемы мне писать обработку для каждого брокера?
Верно.
Мы не можем предусмотреть все сценарии использования QLua, и у нас нет примеров для каждого возможного алгоритма. По этой причине на Учебном сервере QUIK-Junior было включено ведение позиций в календарных датах, чтобы пользователи могли протестировать работу в новом режиме и подготовиться к аналогичным изменениям, если они произойдут на сервере брокера.
Рекомендуем протестировать Ваши скрипты на сервере QUIK-Junior - получить доступ можно по ссылке.
Anton Belonogov написал: Это взаимоисключающие параметры, сервер транслирует на терминалы только один из них, поэтому смысла в дополнительном параметре для QLua нет.
Не согласен. Разве "Дата предстоящих расчетов" рассчитывается без учета "Режима торгов"? Пожалуйста, объясните какие проблемы возникают у брокера и пользователя, если дополнительно к тому как было (limit_kind - "Режим торгов") транслировалось бы поле (возможно пустое) "Дата предстоящих расчетов"? Может быть разработчик не стал создавать новое поле с целью большой экономии памяти ?
Пожалуйста, объясните какие проблемы возникают у брокера и пользователя, если дополнительно к тому как было (limit_kind - "Режим торгов") транслировалось бы поле (возможно пустое) "Дата предстоящих расчетов"?
Повторим, в разрезе позиций на сервере эти параметры - взаимоисключающие, для позиции может быть установлен только один из них. Такова особенность архитектуры системы QUIK, более развернутый комментарий мы не можем предоставить.
На сервере QUIK-Junior доступно осуществление операций шорт по некоторым акциям, в том числе SBER - см. таблицу "Купить/Продать" (открывается двойным нажатием по строке в таблице "Клиентский портфель"), поле "Продажа".
Если при попытке продать какой-либо инструмент из этого списка Вы получаете ошибку, просим прислать ее скриншот.
приходится делать работу за разрабов - это конечно уродство, когда вот так вносятся правки без какой-либо поддержки и указаний как с этим работать. Как минимум - это неуважение к своим клиентам.
Код тестирования:
-- Глобальные переменные для определения схемы работы IS_NEW_DATE_SCHEME = nil -- Флаг новой схемы с датами CURRENT_TRADE_DATE = nil -- Текущая дата торгов TEST_RESULTS = {} -- Таблица для хранения результатов тестирования
-- Константы (должны быть настроены под конкретного брокера) STOCK_ACCOUNT = "NL001***" -- счет для акций CLIENT_CODE = "10***" -- код клиента FIRM_ID = "NC00***" -- код фирмы
-- Функция записи в лог-файл function WriteToLog(message) local log_file = io.open(getScriptPath().."\\quik_scheme_test.txt", "a") if log_file then local timestamp = os.date("%Y-%m-%d %H:%M:%S") log_file:write(timestamp .. " - " .. message .. "\n") log_file:close() else message("Ошибка открытия файла для записи!") end end
-- Функция определения типа схемы (новая с датами или старая) function detect_scheme_type() WriteToLog("=== Начало определения типа схемы ===")
local num = getNumberOf('money_limits') WriteToLog("Найдено записей в money_limits: " .. tostring(num))
if num > 0 then for i = 0, num - 1 do local limit = getItem('money_limits', i) WriteToLog(string.format("Запись %d: limit_kind = %s, firmid = %s, client_code = %s", i, tostring(limit.limit_kind), tostring(limit.firmid), tostring(limit.client_code)))
if limit.limit_kind > 365 then -- Если значение больше 365, это новая схема с датами IS_NEW_DATE_SCHEME = true CURRENT_TRADE_DATE = tonumber(os.date("%Y%m%d")) WriteToLog("Обнаружена НОВАЯ схема с датами. limit_kind = " .. tostring(limit.limit_kind)) WriteToLog("Текущая дата: " .. tostring(CURRENT_TRADE_DATE)) return end end end
IS_NEW_DATE_SCHEME = false WriteToLog("Обнаружена СТАРАЯ схема с T0, T1, T2") end
-- Функция получения даты для T0, T1, T2 function get_settlement_date(offset) if not IS_NEW_DATE_SCHEME then WriteToLog("get_settlement_date: старая схема, возвращаем offset = " .. tostring(offset)) return offset end
local trade_date = CURRENT_TRADE_DATE if not trade_date then trade_date = tonumber(os.date("%Y%m%d")) end
local result_date = trade_date + offset WriteToLog(string.format("get_settlement_date: новая схема, offset=%d, результат=%d", offset, result_date)) return result_date end
-- Функция поиска позиции с учетом схемы function find_limit_position(limits_table, criteria) WriteToLog("Поиск в таблице: " .. limits_table) WriteToLog("Критерии поиска: " .. tostring(criteria.firmid) .. ", " .. tostring(criteria.client_code))
local num = getNumberOf(limits_table) WriteToLog("Количество записей в таблице: " .. tostring(num))
if num <= 0 then WriteToLog("Таблица пуста") return nil end
if IS_NEW_DATE_SCHEME then WriteToLog("Поиск в НОВОЙ схеме (по максимальной дате)") local best_match = nil local max_date = 0
for i = 0, num - 1 do local item = getItem(limits_table, i) local matches = true
for key, value in pairs(criteria) do if key ~= 'limit_kind' and item[key] ~= value then matches = false break end end
if matches then WriteToLog(string.format("Найдена подходящая запись: limit_kind=%d, currentbal=%s", item.limit_kind, tostring(item.currentbal))) if item.limit_kind > max_date then max_date = item.limit_kind best_match = item end end end
if best_match then WriteToLog("Выбрана запись с максимальной датой: " .. tostring(max_date)) else WriteToLog("Подходящих записей не найдено") end
return best_match else WriteToLog("Поиск в СТАРОЙ схеме (точное соответствие)") for i = 0, num - 1 do local item = getItem(limits_table, i) local matches = true
for key, value in pairs(criteria) do if item[key] ~= value then matches = false break end end
if matches then WriteToLog("Найдена точная запись: limit_kind=" .. tostring(item.limit_kind)) return item end end end
WriteToLog("Запись не найдена") return nil end
-- Обновленная функция получения доступных средств function GetFreeMoney() WriteToLog("=== Вызов GetFreeMoney() ===") WriteToLog("CLASS_CODE: " .. tostring(CLASS_CODE))
-- Определяем схему при первом вызове if IS_NEW_DATE_SCHEME == nil then detect_scheme_type() end
if CLASS_CODE == 'SPBFUT' or CLASS_CODE == 'SPBOPT' then WriteToLog("Обработка срочного рынка") local num = getNumberOf('futures_client_limits') WriteToLog("Записей в futures_client_limits: " .. tostring(num))
if num > 0 then for i = 0, num - 1 do local limit = getItem('futures_client_limits', i) if limit.firmid == FIRM_ID and limit.trdaccid == STOCK_ACCOUNT and limit.limit_type == 0 then WriteToLog("Найдены средства срочного рынка: " .. tostring(limit.cbplplanned)) return limit.cbplplanned end end end elseif CLASS_CODE == 'TQBR' or CLASS_CODE == 'QJSIM' or CLASS_CODE == 'CETS' then WriteToLog("Обработка фондового рынка") local criteria = { currcode = 'SUR', firmid = FIRM_ID, client_code = CLIENT_CODE }
if IS_NEW_DATE_SCHEME then WriteToLog("Поиск в НОВОЙ схеме") criteria.limit_kind = 0 local limit = find_limit_position('money_limits', criteria) if limit then WriteToLog("Найдены средства: " .. tostring(limit.currentbal)) return limit.currentbal end else WriteToLog("Поиск в СТАРОЙ схеме") criteria.limit_kind = 0 local limit = find_limit_position('money_limits', criteria) if limit then WriteToLog("Найдены средства: " .. tostring(limit.currentbal)) return limit.currentbal end end else WriteToLog("Неизвестный CLASS_CODE: " .. tostring(CLASS_CODE)) end
WriteToLog("Средства не найдены, возвращаем 0") return 0 end
-- Функция тестирования различных CLASS_CODE function TestDifferentMarkets() WriteToLog("\n=== Тестирование различных рынков ===")
-- Тестируем фондовый рынок CLASS_CODE = 'TQBR' local stock_money = GetFreeMoney() table.insert(TEST_RESULTS, {market = "Фондовый рынок (TQBR)", money = stock_money})
-- Тестируем срочный рынок CLASS_CODE = 'SPBFUT' local futures_money = GetFreeMoney() table.insert(TEST_RESULTS, {market = "Срочный рынок (SPBFUT)", money = futures_money})
-- Тестируем валютный рынок CLASS_CODE = 'CETS' local currency_money = GetFreeMoney() table.insert(TEST_RESULTS, {market = "Валютный рынок (CETS)", money = currency_money}) end
-- Функция анализа таблиц лимитов function AnalyzeLimitTables() WriteToLog("\n=== Анализ таблиц лимитов ===")
-- Анализ money_limits local money_limits_count = getNumberOf('money_limits') WriteToLog("money_limits записей: " .. tostring(money_limits_count))
if money_limits_count > 0 then for i = 0, math.min(money_limits_count-1, 10) do -- Ограничим вывод первыми 10 записями local limit = getItem('money_limits', i) WriteToLog(string.format("money_limits[%d]: firmid=%s, client_code=%s, limit_kind=%s, currentbal=%s", i, tostring(limit.firmid), tostring(limit.client_code), tostring(limit.limit_kind), tostring(limit.currentbal))) end end
-- Анализ depo_limits local depo_limits_count = getNumberOf('depo_limits') WriteToLog("depo_limits записей: " .. tostring(depo_limits_count))
if depo_limits_count > 0 then for i = 0, math.min(depo_limits_count-1, 5) do -- Ограничим вывод первыми 5 записями local limit = getItem('depo_limits', i) WriteToLog(string.format("depo_limits[%d]: firmid=%s, client_code=%s, sec_code=%s, limit_kind=%s, currentbal=%s", i, tostring(limit.firmid), tostring(limit.client_code), tostring(limit.sec_code), tostring(limit.limit_kind), tostring(limit.currentbal))) end end end
-- Основная функция function main() WriteToLog("====== ЗАПУСК ТЕСТИРОВАНИЯ СХЕМЫ QUIK ======") WriteToLog("Версия скрипта: 1.0") WriteToLog("Дата тестирования: " .. os.date("%Y-%m-%d %H:%M:%S"))
-- Инициализация detect_scheme_type()
-- Анализ таблиц AnalyzeLimitTables()
-- Тестирование различных рынков TestDifferentMarkets()
-- Вывод результатов WriteToLog("\n=== ИТОГОВЫЕ РЕЗУЛЬТАТЫ ===") WriteToLog("Тип схемы: " .. (IS_NEW_DATE_SCHEME and "НОВАЯ (по датам)" or "СТАРАЯ (T0,T1,T2)"))
for _, result in ipairs(TEST_RESULTS) do WriteToLog(string.format("%s: %s", result.market, tostring(result.money))) end
WriteToLog("\n=== РЕКОМЕНДАЦИИ ===") if IS_NEW_DATE_SCHEME then WriteToLog("1. Используйте функции поиска по максимальной дате") WriteToLog("2. Обновите все скрипты для работы с новой схемой") WriteToLog("3. Тестируйте на QUIK Junior для адаптации") else WriteToLog("1. Брокер использует классическую схему") WriteToLog("2. Готовьтесь к возможному переходу на новую схему") WriteToLog("3. Протестируйте скрипты на QUIK Junior") end
message("Тестирование завершено. Результаты в файле quik_scheme_test.txt") end
-- Очистка файла при каждом запуске local init_file = io.open(getScriptPath().."\\quik_scheme_test.txt", "w") if init_file then init_file:write("") init_file:close() end