Nikolay написал: Если нет ответа транзакции (т.е. ответа шлюза биржи), то, скорее всего, транзакция не прошла дальше сервера брокера. А значит есть ответ и ошибка самого метода sendTranscation. Где тоже необходимо проверять - ушла ли транзакция на биржу.
При попытке отправки транзакции в неторговое время, sendTranscation возвращает ошибку: Ошибка снятия заявки: Указанная транзакция по указанному классу не найдена: "SPBFUT".
Нашел старые логи по которым видно, что c 08:50 до 08:59 идет аукцион открытия.
function KillOrder (sec_info, order_num)
local transaction = {
["TRANS_ID"] = '1',
["ACTION"] = "KILL_ORDER",
["CLASSCODE"] = sec_info.class_code,
["ORDER_KEY"] = tostring(order_num)
}
local result = sendTransaction(transaction)
if result == '' then return order_num end
for key, val in pairs(transaction) do result = result..'\n'..key.." = "..tostring(val) end
error("\nОшибка снятия заявки: "..result)
end
Проверка осуществляется, ошибки нет, робот продолжил работу.
Я не предлагаю использовать OnTransReply для проверки состояния сессии. OnTransReply вызывается в том числе когда мы выставляем заявку и получаем сообщения: "Сейчас эта сессия не идет" "Идет пром. клиринг, нельзя совершать торговые операции" "Инструмент недопустим в аукционе открытия" "Торги по этому финансовому инструменту сейчас не проводятся" "Превышен лимит отправки транзакций для данного логина" ... И робот видит этот вызов и что он произошел на конкретную транзакцию и дальше обрабатывает эту ошибку.
Сейчас речь идет о том, что при попытке снять заявку вызов OnTransReply не произошел и робот ждет ответ на снятие, но в ответ - ничего. Только сообщение в таблице сообщений которое он не видит. И нам остается проверять состояние сессии и делать предположение о том, что заявка не снята потому-что попытка снятия отклонена с ошибкой "Сейчас эта сессия не идет." А если вызов OnTransReply происходил еще и при снятии, то в робота не пришлось бы включать предположения, он мог бы обработать конкретную ситуацию.
Цитата
Nikolay написал: Подавать их необходимо когда торги по инструменту проводятся.
Цитата
Владислав написал: Мы можем проверять состояние сессий перед отправкой транзакции, но даже так мы будем натыкаться на ошибки транзакций в первые секунды неторгового времени.
paluke написал: А sendTransaction ничего не возвращает?
Не возвращает
Цитата
Ziveleos написал: А зачем Вы отправляете транзакцию, если сессия в это время закрыта?
Транзакция отправляется автоматически в ответ на срабатывание заявки. Заявка сработала в 08:59:21, на что робот среагировал и снял оставшуюся заявку, после чего получил ошибку, которая отобразилась в таблице сообщений.
Цитата
Ziveleos написал: Если нет OnTransReply и сессия закрыта, какой вывод должен сделать робот?
Вывод робот делает, но это как-то костыльно. А если это не ошибка, а долгая задержка ответа или косяки на сервере. Хотелось бы получать конкретный ответ, тем более, учитывая что в таблицу сообщений он приходит.
Мы можем проверять состояние сессий перед отправкой транзакции, но даже так мы будем натыкаться на ошибки транзакций в первые секунды неторгового времени. Поэтому я проверяю состояние сессии после получения ошибки на транзакцию. Как ошибка появляется, тогда уже ждем начала торгов и обрабатываем ошибку.
На данный момент вопрос в том, как роботу получить ответ Ошибка снятия заявки. [GW][3] "Сейчас эта сессия не идет." который отображается в таблице сообщений. Если никак, то нужно реализовать в QUIK такую возможность т.к. OnTransReply вызывается в ответ на выставление и было бы логично вызывать его в ответ на снятие.
Это не решение. Я отправляю транзакцию и хочу получить на нее ответ, чтобы продолжить работу. Пока ответа нет, робот не может выставлять новые заявки. Где роботу взять этот ответ?
Добрый день! При попытке снять заявку возникла ошибка: Ошибка снятия заявки. [GW][3] "Сейчас эта сессия не идет." При этом ни OnOrder, ни OnTransReply небыли вызваны. Как роботу понять что произошла ошибка снятия конкретной заявки, если колбеки не вызываются?
Столкнулся с той-же проблемой. Решил получением необходимых полей из кармана транзакций. Создать окно - Все типы окон - Карман транзакций - ETF - Вод заявки - Добавить все - Да В кармане - ПКМ - Положить в карман - ETF - Ввод заявки - Выполнить - Вводим значения - Да В кармане нажимаем на транзакцию ПКМ - Сохранить в tri-файл - Сохраняем Открываем файл в редакторе кода и копируем поля в вашу функцию отправки транзакции.
В моем случае так:
Код
function SendOrder (sec_info, quantity, price)
local transaction = {
["TRANS_ID"] = getTransId(),
["Торговый счет"] = sec_info.account,
["CLASSCODE"] = sec_info.class_code,
["Режим"] = sec_info.class_code,
["Инструмент"] = sec_info.sec_code,
["Примечание"] = sec_info.client_code,
["Цена"] = tostring(rounding(price, sec_info.scale)),
["Лоты"] = tostring(rounding(math.abs(quantity))),
["К/П"] = quantity > 0 and "Купля" or "Продажа",
["ACTION"] = "Ввод заявки",
["Тип"] = "Лимитная",
["Тип по цене"] = "По разным ценам",
["Тип по остатку"] = "Поставить в очередь",
["Тип ввода значения цены"] = "По цене",
["Назначение заявки"] = "По умолчанию",
["Тип события активации заявки"] = "Обычная заявка",
["Объем заявки"] = "0.0",
}
local result = sendTransaction(transaction)
if result == '' then
return tonumber(transaction.TRANS_ID)
end
for key, val in pairs(transaction) do result = result..'\n'..key.." = "..tostring(val) end
error("\nОшибка транзакции: "..result..'\n'..debug.traceback())
end
Хотя до это момента, был клиент который тоже заказывал для ETF и у него работало с таким набором полей: "TRANS_ID", "CLASSCODE", "SECCODE", "ACCOUNT", "OPERATION", "QUANTITY", "PRICE", "ACTION", "CLIENT_CODE"
uuh написал: Не то что слышал, а лично видел. При этом система мне так же не давала ставить заявки за планкой. Но кто-то их туда точно ставил и сделки за планкой проходили.
Я заметил, что если до клиринга выставить заявку, а после клиринга планка сместится, то наша заявка может остаться за планкой. Таким образом контрагент может свести нашу заявку с заявками из стаканов фьючерса, при том, что она будет за планкой.
Здравствуйте. Несколько раз встречал информацию, что на календарных спредах, роботы выставляли заявки за планкой. Я пробовал выставлять вручную и через lua, итог один "Цена сделки вне лимита", при этом видно, что за планкой проходят сделки. Буду благодарен, если подскажете почему так происходит или где можно найти об этом информацию ? И если работаете с данным классом инструментов, то какая у Вас логика работы с планкой ?
Прошу прощенья, архив скачивал не 32 битный, а 64. При скачивании 32 битного, появлялась ошибка D:\DEVELOPMENT\Exchange\Роботы\iup\iuptest.lua:1: %1 не является приложением Win32.
Владимир, nikolz, Благодарю за советы. На примере кода представленного Антоном выше, сделал тесты скорости получения ответа на транзакцию. По итогу думаю, что в рамках нескольких скриптов действительно смысла с заморочками нет. Тем более если скрипт работает с различными таблицами в QUIK. В моем случае, после отправки транзакции, происходит расчет прибыли от совершенной сделки, запись этой сделки в файл и т.п. Для того, что бы исключить возможные несостыковки, связанные с длительностью поступления данных в таблицу сделок и таблицу поз. по. кл. счетам, скрипт делает паузу в 1000мс. При такой реализации программы, смысла в заморочках точно нет. Но если речь идет о большом количестве скриптов, в которых все завязано на коллбеках, смысл думаю что есть.
Код для тестирования скорости:
Скрытый текст
Код
_G.client = {}
_G.client.account = "SPBFUT000he"
_G.client.class_code = "SPBFUT"
_G.client.sec_code = "BRV2"
_G.time = 0
local function toftime(a)
local aa = a.hour
aa = aa * 60
aa = aa + a.min
aa = aa * 60
aa = aa + a.sec
aa = aa * 1000000
aa = aa + a.mcs
return aa
end
function OnTransReply (trans_reply)
if trans_reply.sec_code == _G.client.sec_code then
local x = toftime(os.sysdate()) - toftime(_G.time)
PrintDbgStr("OnTransReply: "..(x).."мкс "..(x / 1000).."мс "..(x / 1000000).."с")
end
end
function OnTrade (trade)
if trade.sec_code == _G.client.sec_code then
local x = toftime(os.sysdate()) - toftime(_G.time)
PrintDbgStr("OnTrade: "..(x).."мкс "..(x / 1000).."мс "..(x / 1000000).."с")
end
end
function sendMarketOrder (client, quantity, comment)
local number = getNumberOf("trades")
local transaction = {
["TRANS_ID"] = '1',
["CLASSCODE"] = client.class_code,
["SECCODE"] = client.sec_code,
['ACCOUNT'] = client.account,
['OPERATION'] = quantity > 0 and 'B' or 'S',
['QUANTITY'] = tostring(math.abs(quantity)),
["ACTION"] = "NEW_ORDER",
["TYPE"] = "M",
['PRICE'] = "0",
["CLIENT_CODE"] = comment
}
_G.time = os.sysdate()
local result = sendTransaction(transaction)
if result == '' then
for count = 1, 30000 do
local new_number = getNumberOf("trades")
if new_number ~= number then
for i = new_number -1, 0, -1 do
local item = getItem("trades", i)
if item.trans_id == tonumber(transaction.TRANS_ID) then
local x = toftime(os.sysdate()) - toftime(_G.time)
PrintDbgStr("sendMarketOrder: "..(x).."мкс "..(x / 1000).."мс "..(x / 1000000).."с "..count)
return true
end
end
end
sleep(1)
end
end
local fields = ""
for k, val in pairs(transaction) do
val = tostring(val)
fields = fields == '' and fields..k.." = ".."\""..val.."\"" or fields..", "..k.." = ".."\""..val.."\""
end
PrintDbgStr("sendMarketOrder: Ошибка транзакции: "..result..' '..fields)
return false
end
function main()
for i = 1, 10 do
PrintDbgStr("i: "..i)
sendMarketOrder(_G.client, 1)
sleep(3000)
end
end
nikolz написал: Существующая структура QLUA имеет существенный недостаток. Все колбеки вызываются из одного потока и каждый из них повторяется в различных скриптах. В итоге получается дублирование одних и тех же действий многократно. Я устранил эту проблему и сделал механизм при котором колбеки в скриптах не повторяются, а скрипты могут запускать функции друг у друга и получать данные из других скриптов.. ------------------------- В итоге не только повышается скорость, но и размер кода сокращается в десятки раз.
Цитата
nikolz написал: Тот механизм, о котором я написал, позволяет очень просто делать много роботов по различным алгоритмом для одного инструмента. Колбеки не дублируются в скриптах Каждый колбек существует в своем скрипте и вызывается всего один раз квиком для получения данных вне зависимости от числа роботов. Роботы получают требуемые данные от этого скрипта. т е потоки синхронизируются и обмениваются данными , а также чтобы не дублировать код, могут запускать функции других скриптов через механизм колбеков между скриптами. Сравнительно просто в этом варианте отдавать данные совершенно независимым процессам.
Здравствуйте. Подскажите пожалуйста, как реализовать подобный механизм ?
Здравствуйте. Написал индикатор, добавляющий на график метку с номером последней свечи. После добавления индикатора на график, изменении его свойств в настройках графика и смене тайм-фрейма ошибок не происходит. Но при смене инструмента (график привязан к таблице "Текущие торги"), на графике появляется дополнительная мистическая метка. Функция AddLabel не возвращает номер этой метки, ее нельзя удалить вручную (перетаскивать по графику можно) и с помощью функции DelLabel, но при этом она удаляется функцией DelAllLabels.
Добавляем индикатор на график и меняем инструмент. На графике появится новая метка и поверх нее еще одна мистическая. Мистическую перетаскиваем в сторону, заходим в настройки индикатора, меняем параметр "Отступ от максимума" (или просто меняем тайм-фрейм) и видим, что мистическая метка остается на месте а вторая изменяется.
По умолчанию, для удаления меток при их перерисовке, индикатор использует функцию DelLabel. Но, если в поле параметра "Удаление всех меток" поставить значение "да [да/нет]", то индикатор будет использовать функцию DelAllLabels и в таком случае, при изменении настроек индикатора или смене тайм-фрейма, мистическая метка будет удаляться.
Если я правильно понял, то данная проблема уже обсуждалась и была решена (topic3532)
Код
-- [[ Индикатор добавляет на график метку с номером последней свечи ]]--
-- Заметки:
-- GetLabelParams возвращает таблицу с названиями параметров в нижнем регистре, тогда как эти параметры в функции AddLabel задаются в верхнем регистре.
-- GetLabelParams возвращает значения всех параметров в строковом виде, несмотря на то, что часть параметров типа number.
-- getDataSourceInfo().class_code работает только после вызова OnChangeSettings()
-- getNumCandles() и getParamEx() работают только после вызова OnCalculate()
Settings = {}
Settings.Name = "- IndexCandleFF"
Settings["Отступ от максимума"] = 100
Settings["Идентификатор графика"] = "one_graph"
Settings["Удаление всех меток"] = "нет [да/нет]"
TableLabel = {}
index_old = 0 -- Функция OnCalculate вызывается несколько раз для каждой свечи. index_old нужен для того, что бы не захламлять таблицу TableLabel.
flag_error = true -- В случае ошибки параметров в настройках индикатора, предотвращает повторный вывод сообщения об ошибке и добавление меток на график.
flag_first = true -- Предотвращаем повторный вызов OnChangeSettings.
flag_init = false -- Флаг для корректной работы getParamEx и getNumCandles
del_labels = string.match(Settings["Удаление всех меток"], "(.*) .*")
interval = 0
function isChartExist (chart_name) -- Возвращает true, если график с идентификатором "Идентификатор графика" существует, иначе false
if chart_name == nil or chart_name == '' then return false end
local n = getNumCandles (chart_name)
if n == nil or n < 1 then return false end
return true
end
function place_label(index)
PrintDbgStr("place_label")
local positionY = H(index) + Settings["Отступ от максимума"]
local DateTime = T(index)
local Y = DateTime.year
local M = DateTime.month if #tostring(M) == 1 then M = '0'..M end
local D = DateTime.day if #tostring(D) == 1 then D = '0'..D end
local HO = DateTime.hour if #tostring(HO) == 1 then HO = '0'..HO end
local MI = DateTime.min if #tostring(MI) == 1 then MI = '0'..MI end
local SE = DateTime.sec if #tostring(SE) == 1 then SE = '0'..SE end
label_params = {}
-- label_params.IMAGE_PATH = getScriptPath() .. "\\1.bmp"
label_params.YVALUE = positionY -- Значение параметра на оси Y, к которому будет привязана метка
label_params.DATE = Y..M..D -- Дата в формате «ГГГГММДД», к которой привязана метка
label_params.TIME = HO..MI..SE -- Время в формате «ЧЧММСС», к которому будет привязана метка
label_params.TRANSPARENCY = 0 -- Прозрачность метки в процентах. Значение должно быть в промежутке [0; 100]. Применяется только для картинки.
label_params.FONT_FACE_NAME = "Arial" -- Название шрифта (например «Arial»)
label_params.FONT_HEIGHT = 8 -- Размер шрифта
label_params.TEXT = tostring(index) -- Если подпись не требуется то оставить строку пустой ""
label_params.HINT = "Index candle" -- Текст всплывающей подсказки
label_params.R = 0 -- Красная компонента цвета в формате RGB. Число в интервале [0;255]
label_params.G = 0 -- Зеленая компонента цвета в формате RGB. Число в интервале [0;255]
label_params.B = 0 -- Синяя компонента цвета в формате RGB. Число в интервале [0;255]
label_params.TRANSPARENT_BACKGROUND = 1
table.insert(TableLabel, AddLabel(Settings["Идентификатор графика"], label_params)) -- Добавляем метку и запоминаем ее ID
end
function Init ()
PrintDbgStr("Init")
flag_init = false
return 1
end
function OnDestroy ()
PrintDbgStr("OnDestroy")
if del_labels == "да" then
DelAllLabels(Settings["Идентификатор графика"])
else
-- for label_id = 1, #TableLabel do
-- DelLabel(Settings["Идентификатор графика"], TableLabel[label_id])
-- end
for label_id = 1, 100000 do -- 100000 - максимально возможное количество меток на графике.
local L = GetLabelParams(Settings["Идентификатор графика"], label_id) -- table or nil
if L ~= nil then
if L.hint == "Index candle" then
DelLabel(Settings["Идентификатор графика"], label_id)
end
end
end
end
index_old = 0
end
function OnChangeSettings()
-- Функция вызывается при редактировании свойств индикатора после нажатия кнопок «Применить» или «OK».
PrintDbgStr("OnChangeSettings")
if flag_init then -- При переходе на другой тайм-фрейм, сначала вызывается функция Init, затем OnChangeSettings затем OnCalculate, но для работы функций getParamEx и getNumCandles необходимо, что бы сначала была вызвана OnCalculate, поэтому, сначала игнорируем автоматический вызов OnChangeSettings, а затем после того как вызовется OnCalculate, вызываем ее сами.
PrintDbgStr("OnChangeSettings_Body")
del_labels = string.match(Settings["Удаление всех меток"], "(.*) .*")
--[ Исключение ошибок в настройках графика ]--
if not isChartExist(Settings["Идентификатор графика"]) then
if flag_error then
message("Ошибка значения в поле: \"Идентификатор графика\"")
flag_error = false
end
elseif del_labels ~= "да" and del_labels ~= "нет" then
if flag_error then
message("Ошибка значения в поле: \"Удаление всех меток\"")
flag_error = false
end
else flag_error = true
end
--[ Удаление меток с графика ]--
if flag_error then
OnDestroy()
end
--[ Задаем отступ для метки ]--
local sec_code = getDataSourceInfo().sec_code
local class_code = getDataSourceInfo().class_code
local step = getParamEx(class_code, sec_code, "SEC_PRICE_STEP").param_image -- Минимальный шаг цены
local scale = tonumber(getParamEx(class_code, sec_code, "SEC_SCALE").param_image) -- Точность цены.
if scale == nil then
error("local ER: Для корректной работы индикатора необходимо установить соединение с сервером !")
else
if scale > 0 then
step = tonumber("0."..step:match(",(.*)")) -- Заменяем запятую на точку и преобразуем строковый формат в числовой.
end
Settings["Отступ от максимума"] = step * Settings["Отступ от максимума"]
end
flag_first = false -- Предотвращаем повторный вызов OnChangeSettings.
interval = getDataSourceInfo().interval
end
end
function OnCalculate (index)
-- Если изменился тайм-фрейм. При изменении тайм-фрейма необходимо удалить старые метки.
if interval ~= getDataSourceInfo().interval then
flag_init = true
PrintDbgStr("interval")
OnDestroy()
interval = getDataSourceInfo().interval
end
if index > index_old then
if flag_first then
PrintDbgStr("flag_first")
OnChangeSettings()
end
--[ Добавление меток на график ]--
if flag_error then
if index == Size() then
place_label(index)
end
end
index_old = index
end
return
end