Initial commit

This commit is contained in:
Игорь Чечет
2021-02-14 15:47:51 +05:00
commit 87425dfe9c
16 changed files with 3512 additions and 0 deletions

140
QUIK/lua/QuikSharp.lua Normal file
View File

@@ -0,0 +1,140 @@
--~ Copyright (c) 2014-2020 QUIKSharp Authors https://github.com/finsight/QUIKSharp/blob/master/AUTHORS.md. All rights reserved.
--~ Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.
-- is running from Quik
function is_quik()
if getScriptPath then return true else return false end
end
quikVersion = nil
script_path = "."
if is_quik() then
script_path = getScriptPath()
quikVersion = getInfoParam("VERSION")
if quikVersion ~= nil then
local t={}
for str in string.gmatch(quikVersion, "([^%.]+)") do
table.insert(t, str)
end
quikVersion = tonumber(t[1]) * 100 + tonumber(t[2])
end
if quikVersion == nil then
message("QUIK# cannot detect QUIK version", 3)
return
else
libPath = "\\clibs"
end
-- MD dynamic, requires MSVCRT
-- MT static, MSVCRT is linked statically with luasocket
-- package.cpath contains info.exe working directory, which has MSVCRT, so MT should not be needed in theory,
-- but in one issue someone said it doesn't work on machines that do not have Visual Studio.
local linkage = "MD"
if quikVersion >= 805 then
libPath = libPath .. "64\\53_"..linkage.."\\"
elseif quikVersion >= 800 then
libPath = libPath .. "64\\5.1_"..linkage.."\\"
else
libPath = "\\clibs\\5.1_"..linkage.."\\"
end
end
package.path = package.path .. ";" .. script_path .. "\\?.lua;" .. script_path .. "\\?.luac"..";"..".\\?.lua;"..".\\?.luac"
package.cpath = package.cpath .. ";" .. script_path .. libPath .. '?.dll'..";".. '.' .. libPath .. '?.dll'
local util = require("qsutils")
local qf = require("qsfunctions")
require("qscallbacks")
log("Detected Quik version: ".. quikVersion .." and using cpath: "..package.cpath , 0)
local is_started = true
-- we need two ports since callbacks and responses conflict and write to the same socket at the same time
-- I do not know how to make locking in Lua, it is just simpler to have two independent connections
-- To connect to a remote terminal - replace '127.0.0.1' with the terminal ip-address
-- All this values could be replaced with values from config.json
local response_host = '127.0.0.1'
local response_port = 34130
local callback_host = '127.0.0.1'
local callback_port = response_port + 1
function do_main()
log("Entered main function", 0)
while is_started do
-- if not connected, connect
util.connect(response_host, response_port, callback_host, callback_port)
-- when connected, process queue
-- receive message,
local requestMsg = receiveRequest()
if requestMsg then
-- if ok, process message
-- dispatch_and_process never throws, it returns lua errors wrapped as a message
local responseMsg, err = qf.dispatch_and_process(requestMsg)
if responseMsg then
-- send message
local res = sendResponse(responseMsg)
else
log("Could not dispatch and process request: " .. err, 3)
end
else
delay(1)
end
end
end
function main()
setup("QuikSharp")
run()
end
--- catch errors
function run()
local status, err = pcall(do_main)
if status then
log("finished")
else
log(err, 3)
end
end
function setup(script_name)
if not script_name then
log("File name of this script is unknown. Please, set it explicity instead of scriptFilename() call inside your custom file", 3)
return false
end
local list = paramsFromConfig(script_name)
if list then
response_host = list[1]
response_port = list[2]
callback_host = list[3]
callback_port = list[4]
printRunningMessage(script_name)
elseif script_name == "QuikSharp" then
-- use default values for this file in case no custom config found for it
printRunningMessage(script_name)
else -- do nothing when config is not found
log("File config.json is not found or contains no entries for this script name: " .. script_name, 3)
return false
end
return true
end
function printRunningMessage(script_name)
log("Running from ".. script_name .. ", params: response " .. response_host .. ":" .. response_port ..", callback ".. " ".. callback_host ..":".. callback_port)
end
if not is_quik() then
log("Hello, QUIK#! Running outside Quik.")
setup("QuikSharp")
do_main()
logfile:close()
end

15
QUIK/lua/Quik_2.lua Normal file
View File

@@ -0,0 +1,15 @@
--~ Copyright (c) 2014-2020 QUIKSharp Authors https://github.com/finsight/QUIKSharp/blob/master/AUTHORS.md. All rights reserved.
--~ Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.
script_path = getScriptPath()
package.path = package.path .. ";" .. script_path .. "\\?.lua;" .. script_path .. "\\?.luac"..";"..".\\?.lua;"..".\\?.luac"
require("QuikSharp")
-- Do not edit this file. Just copy it and save with a different name. Then write required params for it inside config.json file
-- Не редактируйте этой файл. Просто скопируйте и сохраните под другим именем. После этого укажите настройки для него в файле config.json
function main()
if setup(scriptFilename()) then
run()
end
end

18
QUIK/lua/config.json Normal file
View File

@@ -0,0 +1,18 @@
{
"servers": [
{
"scriptName": "QuikSharp",
"responseHostname": "*",
"responsePort": 34130,
"callbackHostname": "*",
"callbackPort": 34131
},
{
"scriptName": "Quik_2",
"responseHostname": "*",
"responsePort": 34132,
"callbackHostname": "*",
"callbackPort": 34133
}
]
}

714
QUIK/lua/dkjson.lua Normal file
View File

@@ -0,0 +1,714 @@
-- Module options:
local always_try_using_lpeg = true
local register_global_module_table = false
local global_module_name = 'json'
--[==[
David Kolf's JSON module for Lua 5.1/5.2
Version 2.5
For the documentation see the corresponding readme.txt or visit
<http://dkolf.de/src/dkjson-lua.fsl/>.
You can contact the author by sending an e-mail to 'david' at the
domain 'dkolf.de'.
Copyright (C) 2010-2013 David Heiko Kolf
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--]==]
-- global dependencies:
local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset =
pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset
local error, require, pcall, select = error, require, pcall, select
local floor, huge = math.floor, math.huge
local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat =
string.rep, string.gsub, string.sub, string.byte, string.char,
string.find, string.len, string.format
local strmatch = string.match
local concat = table.concat
local json = { version = "dkjson 2.5" }
if register_global_module_table then
_G[global_module_name] = json
end
local _ENV = nil -- blocking globals in Lua 5.2
pcall (function()
-- Enable access to blocked metatables.
-- Don't worry, this module doesn't change anything in them.
local debmeta = require "debug".getmetatable
if debmeta then getmetatable = debmeta end
end)
json.null = setmetatable ({}, {
__tojson = function () return "null" end
})
local function isarray (tbl)
local max, n, arraylen = 0, 0, 0
for k,v in pairs (tbl) do
if k == 'n' and type(v) == 'number' then
arraylen = v
if v > max then
max = v
end
else
if type(k) ~= 'number' or k < 1 or floor(k) ~= k then
return false
end
if k > max then
max = k
end
n = n + 1
end
end
if max > 10 and max > arraylen and max > n * 2 then
return false -- don't create an array with too many holes
end
return true, max
end
local escapecodes = {
["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"
}
local function escapeutf8 (uchar)
local value = escapecodes[uchar]
if value then
return value
end
local a, b, c, d = strbyte (uchar, 1, 4)
a, b, c, d = a or 0, b or 0, c or 0, d or 0
if a <= 0x7f then
value = a
elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then
value = (a - 0xc0) * 0x40 + b - 0x80
elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then
value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80
elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then
value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80
else
return ""
end
if value <= 0xffff then
return strformat ("\\u%.4x", value)
elseif value <= 0x10ffff then
-- encode as UTF-16 surrogate pair
value = value - 0x10000
local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400)
return strformat ("\\u%.4x\\u%.4x", highsur, lowsur)
else
return ""
end
end
local function fsub (str, pattern, repl)
-- gsub always builds a new string in a buffer, even when no match
-- exists. First using find should be more efficient when most strings
-- don't contain the pattern.
if strfind (str, pattern) then
return gsub (str, pattern, repl)
else
return str
end
end
local function quotestring (value)
-- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js
value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8)
if strfind (value, "[\194\216\220\225\226\239]") then
value = fsub (value, "\194[\128-\159\173]", escapeutf8)
value = fsub (value, "\216[\128-\132]", escapeutf8)
value = fsub (value, "\220\143", escapeutf8)
value = fsub (value, "\225\158[\180\181]", escapeutf8)
value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8)
value = fsub (value, "\226\129[\160-\175]", escapeutf8)
value = fsub (value, "\239\187\191", escapeutf8)
value = fsub (value, "\239\191[\176-\191]", escapeutf8)
end
return "\"" .. value .. "\""
end
json.quotestring = quotestring
local function replace(str, o, n)
local i, j = strfind (str, o, 1, true)
if i then
return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1)
else
return str
end
end
-- locale independent num2str and str2num functions
local decpoint, numfilter
local function updatedecpoint ()
decpoint = strmatch(tostring(0.5), "([^05+])")
-- build a filter that can be used to remove group separators
numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+"
end
updatedecpoint()
local function num2str (num)
return replace(fsub(tostring(num), numfilter, ""), decpoint, ".")
end
local function str2num (str)
local num = tonumber(replace(str, ".", decpoint))
if not num then
updatedecpoint()
num = tonumber(replace(str, ".", decpoint))
end
return num
end
local function addnewline2 (level, buffer, buflen)
buffer[buflen+1] = "\n"
buffer[buflen+2] = strrep (" ", level)
buflen = buflen + 2
return buflen
end
function json.addnewline (state)
if state.indent then
state.bufferlen = addnewline2 (state.level or 0,
state.buffer, state.bufferlen or #(state.buffer))
end
end
local encode2 -- forward declaration
local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state)
local kt = type (key)
if kt ~= 'string' and kt ~= 'number' then
return nil, "type '" .. kt .. "' is not supported as a key by JSON."
end
if prev then
buflen = buflen + 1
buffer[buflen] = ","
end
if indent then
buflen = addnewline2 (level, buffer, buflen)
end
buffer[buflen+1] = quotestring (key)
buffer[buflen+2] = ":"
return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state)
end
local function appendcustom(res, buffer, state)
local buflen = state.bufferlen
if type (res) == 'string' then
buflen = buflen + 1
buffer[buflen] = res
end
return buflen
end
local function exception(reason, value, state, buffer, buflen, defaultmessage)
defaultmessage = defaultmessage or reason
local handler = state.exception
if not handler then
return nil, defaultmessage
else
state.bufferlen = buflen
local ret, msg = handler (reason, value, state, defaultmessage)
if not ret then return nil, msg or defaultmessage end
return appendcustom(ret, buffer, state)
end
end
function json.encodeexception(reason, value, state, defaultmessage)
return quotestring("<" .. defaultmessage .. ">")
end
encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state)
local valtype = type (value)
local valmeta = getmetatable (value)
valmeta = type (valmeta) == 'table' and valmeta -- only tables
local valtojson = valmeta and valmeta.__tojson
if valtojson then
if tables[value] then
return exception('reference cycle', value, state, buffer, buflen)
end
tables[value] = true
state.bufferlen = buflen
local ret, msg = valtojson (value, state)
if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end
tables[value] = nil
buflen = appendcustom(ret, buffer, state)
elseif value == nil then
buflen = buflen + 1
buffer[buflen] = "null"
elseif valtype == 'number' then
local s
if value ~= value or value >= huge or -value >= huge then
-- This is the behaviour of the original JSON implementation.
s = "null"
else
s = num2str (value)
end
buflen = buflen + 1
buffer[buflen] = s
elseif valtype == 'boolean' then
buflen = buflen + 1
buffer[buflen] = value and "true" or "false"
elseif valtype == 'string' then
buflen = buflen + 1
buffer[buflen] = quotestring (value)
elseif valtype == 'table' then
if tables[value] then
return exception('reference cycle', value, state, buffer, buflen)
end
tables[value] = true
level = level + 1
local isa, n = isarray (value)
if n == 0 and valmeta and valmeta.__jsontype == 'object' then
isa = false
end
local msg
if isa then -- JSON array
buflen = buflen + 1
buffer[buflen] = "["
for i = 1, n do
buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
if i < n then
buflen = buflen + 1
buffer[buflen] = ","
end
end
buflen = buflen + 1
buffer[buflen] = "]"
else -- JSON object
local prev = false
buflen = buflen + 1
buffer[buflen] = "{"
local order = valmeta and valmeta.__jsonorder or globalorder
if order then
local used = {}
n = #order
for i = 1, n do
local k = order[i]
local v = value[k]
if v then
used[k] = true
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
prev = true -- add a seperator before the next element
end
end
for k,v in pairs (value) do
if not used[k] then
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
prev = true -- add a seperator before the next element
end
end
else -- unordered
for k,v in pairs (value) do
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
prev = true -- add a seperator before the next element
end
end
if indent then
buflen = addnewline2 (level - 1, buffer, buflen)
end
buflen = buflen + 1
buffer[buflen] = "}"
end
tables[value] = nil
else
return exception ('unsupported type', value, state, buffer, buflen,
"type '" .. valtype .. "' is not supported by JSON.")
end
return buflen
end
function json.encode (value, state)
state = state or {}
local oldbuffer = state.buffer
local buffer = oldbuffer or {}
state.buffer = buffer
updatedecpoint()
local ret, msg = encode2 (value, state.indent, state.level or 0,
buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state)
if not ret then
error (msg, 2)
elseif oldbuffer == buffer then
state.bufferlen = ret
return true
else
state.bufferlen = nil
state.buffer = nil
return concat (buffer)
end
end
local function loc (str, where)
local line, pos, linepos = 1, 1, 0
while true do
pos = strfind (str, "\n", pos, true)
if pos and pos < where then
line = line + 1
linepos = pos
pos = pos + 1
else
break
end
end
return "line " .. line .. ", column " .. (where - linepos)
end
local function unterminated (str, what, where)
return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where)
end
local function scanwhite (str, pos)
while true do
pos = strfind (str, "%S", pos)
if not pos then return nil end
local sub2 = strsub (str, pos, pos + 1)
if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then
-- UTF-8 Byte Order Mark
pos = pos + 3
elseif sub2 == "//" then
pos = strfind (str, "[\n\r]", pos + 2)
if not pos then return nil end
elseif sub2 == "/*" then
pos = strfind (str, "*/", pos + 2)
if not pos then return nil end
pos = pos + 2
else
return pos
end
end
end
local escapechars = {
["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f",
["n"] = "\n", ["r"] = "\r", ["t"] = "\t"
}
local function unichar (value)
if value < 0 then
return nil
elseif value <= 0x007f then
return strchar (value)
elseif value <= 0x07ff then
return strchar (0xc0 + floor(value/0x40),
0x80 + (floor(value) % 0x40))
elseif value <= 0xffff then
return strchar (0xe0 + floor(value/0x1000),
0x80 + (floor(value/0x40) % 0x40),
0x80 + (floor(value) % 0x40))
elseif value <= 0x10ffff then
return strchar (0xf0 + floor(value/0x40000),
0x80 + (floor(value/0x1000) % 0x40),
0x80 + (floor(value/0x40) % 0x40),
0x80 + (floor(value) % 0x40))
else
return nil
end
end
local function scanstring (str, pos)
local lastpos = pos + 1
local buffer, n = {}, 0
while true do
local nextpos = strfind (str, "[\"\\]", lastpos)
if not nextpos then
return unterminated (str, "string", pos)
end
if nextpos > lastpos then
n = n + 1
buffer[n] = strsub (str, lastpos, nextpos - 1)
end
if strsub (str, nextpos, nextpos) == "\"" then
lastpos = nextpos + 1
break
else
local escchar = strsub (str, nextpos + 1, nextpos + 1)
local value
if escchar == "u" then
value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16)
if value then
local value2
if 0xD800 <= value and value <= 0xDBff then
-- we have the high surrogate of UTF-16. Check if there is a
-- low surrogate escaped nearby to combine them.
if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then
value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16)
if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then
value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000
else
value2 = nil -- in case it was out of range for a low surrogate
end
end
end
value = value and unichar (value)
if value then
if value2 then
lastpos = nextpos + 12
else
lastpos = nextpos + 6
end
end
end
end
if not value then
value = escapechars[escchar] or escchar
lastpos = nextpos + 2
end
n = n + 1
buffer[n] = value
end
end
if n == 1 then
return buffer[1], lastpos
elseif n > 1 then
return concat (buffer), lastpos
else
return "", lastpos
end
end
local scanvalue -- forward declaration
local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta)
local len = strlen (str)
local tbl, n = {}, 0
local pos = startpos + 1
if what == 'object' then
setmetatable (tbl, objectmeta)
else
setmetatable (tbl, arraymeta)
end
while true do
pos = scanwhite (str, pos)
if not pos then return unterminated (str, what, startpos) end
local char = strsub (str, pos, pos)
if char == closechar then
return tbl, pos + 1
end
local val1, err
val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
if err then return nil, pos, err end
pos = scanwhite (str, pos)
if not pos then return unterminated (str, what, startpos) end
char = strsub (str, pos, pos)
if char == ":" then
if val1 == nil then
return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")"
end
pos = scanwhite (str, pos + 1)
if not pos then return unterminated (str, what, startpos) end
local val2
val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
if err then return nil, pos, err end
tbl[val1] = val2
pos = scanwhite (str, pos)
if not pos then return unterminated (str, what, startpos) end
char = strsub (str, pos, pos)
else
n = n + 1
tbl[n] = val1
end
if char == "," then
pos = pos + 1
end
end
end
scanvalue = function (str, pos, nullval, objectmeta, arraymeta)
pos = pos or 1
pos = scanwhite (str, pos)
if not pos then
return nil, strlen (str) + 1, "no valid JSON value (reached the end)"
end
local char = strsub (str, pos, pos)
if char == "{" then
return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta)
elseif char == "[" then
return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta)
elseif char == "\"" then
return scanstring (str, pos)
else
local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos)
if pstart then
local number = str2num (strsub (str, pstart, pend))
if number then
return number, pend + 1
end
end
pstart, pend = strfind (str, "^%a%w*", pos)
if pstart then
local name = strsub (str, pstart, pend)
if name == "true" then
return true, pend + 1
elseif name == "false" then
return false, pend + 1
elseif name == "null" then
return nullval, pend + 1
end
end
return nil, pos, "no valid JSON value at " .. loc (str, pos)
end
end
local function optionalmetatables(...)
if select("#", ...) > 0 then
return ...
else
return {__jsontype = 'object'}, {__jsontype = 'array'}
end
end
function json.decode (str, pos, nullval, ...)
local objectmeta, arraymeta = optionalmetatables(...)
return scanvalue (str, pos, nullval, objectmeta, arraymeta)
end
function json.use_lpeg ()
local g = require ("lpeg")
if g.version() == "0.11" then
error "due to a bug in LPeg 0.11, it cannot be used for JSON matching"
end
local pegmatch = g.match
local P, S, R = g.P, g.S, g.R
local function ErrorCall (str, pos, msg, state)
if not state.msg then
state.msg = msg .. " at " .. loc (str, pos)
state.pos = pos
end
return false
end
local function Err (msg)
return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall)
end
local SingleLineComment = P"//" * (1 - S"\n\r")^0
local MultiLineComment = P"/*" * (1 - P"*/")^0 * P"*/"
local Space = (S" \n\r\t" + P"\239\187\191" + SingleLineComment + MultiLineComment)^0
local PlainChar = 1 - S"\"\\\n\r"
local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars
local HexDigit = R("09", "af", "AF")
local function UTF16Surrogate (match, pos, high, low)
high, low = tonumber (high, 16), tonumber (low, 16)
if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then
return true, unichar ((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)
else
return false
end
end
local function UTF16BMP (hex)
return unichar (tonumber (hex, 16))
end
local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit))
local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP
local Char = UnicodeEscape + EscapeSequence + PlainChar
local String = P"\"" * g.Cs (Char ^ 0) * (P"\"" + Err "unterminated string")
local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0))
local Fractal = P"." * R"09"^0
local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1
local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num
local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1)
local SimpleValue = Number + String + Constant
local ArrayContent, ObjectContent
-- The functions parsearray and parseobject parse only a single value/pair
-- at a time and store them directly to avoid hitting the LPeg limits.
local function parsearray (str, pos, nullval, state)
local obj, cont
local npos
local t, nt = {}, 0
repeat
obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state)
if not npos then break end
pos = npos
nt = nt + 1
t[nt] = obj
until cont == 'last'
return pos, setmetatable (t, state.arraymeta)
end
local function parseobject (str, pos, nullval, state)
local obj, key, cont
local npos
local t = {}
repeat
key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state)
if not npos then break end
pos = npos
t[key] = obj
until cont == 'last'
return pos, setmetatable (t, state.objectmeta)
end
local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray) * Space * (P"]" + Err "']' expected")
local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject) * Space * (P"}" + Err "'}' expected")
local Value = Space * (Array + Object + SimpleValue)
local ExpectedValue = Value + Space * Err "value expected"
ArrayContent = Value * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
local Pair = g.Cg (Space * String * Space * (P":" + Err "colon expected") * ExpectedValue)
ObjectContent = Pair * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
local DecodeValue = ExpectedValue * g.Cp ()
function json.decode (str, pos, nullval, ...)
local state = {}
state.objectmeta, state.arraymeta = optionalmetatables(...)
local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state)
if state.msg then
return nil, state.pos, state.msg
else
return obj, retpos
end
end
-- use this function only once:
json.use_lpeg = function () return json end
json.using_lpeg = true
return json -- so you can get the module using json = require "dkjson".use_lpeg()
end
if always_try_using_lpeg then
pcall (json.use_lpeg)
end
return json

235
QUIK/lua/qscallbacks.lua Normal file
View File

@@ -0,0 +1,235 @@
--~ Copyright (c) 2014-2020 QUIKSharp Authors https://github.com/finsight/QUIKSharp/blob/master/AUTHORS.md. All rights reserved.
--~ Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.
package.path = package.path..";"..".\\?.lua;"..".\\?.luac"
local qscallbacks = {}
local function CleanUp()
closeLog()
end
function OnQuikSharpDisconnected()
-- TODO any recovery or risk management logic here
end
function OnError(message)
if is_connected then
local msg = {}
msg.t = timemsec()
msg.cmd = "lua_error"
msg.data = "Lua error: " .. message
sendCallback(msg)
end
end
function OnDisconnected()
local msg = {}
msg.cmd = "OnDisconnected"
msg.t = timemsec()
msg.data = ""
sendCallback(msg)
end
function OnConnected()
local msg = {}
msg.cmd = "OnConnected"
msg.t = timemsec()
msg.data = ""
sendCallback(msg)
end
function OnAllTrade(alltrade)
if is_connected then
local msg = {}
msg.t = timemsec()
msg.cmd = "OnAllTrade"
msg.data = alltrade
sendCallback(msg)
end
end
function OnClose()
if is_connected then
local msg = {}
msg.cmd = "OnClose"
msg.t = timemsec()
msg.data = ""
sendCallback(msg)
end
CleanUp()
end
function OnInit(script_path)
if is_connected then
local msg = {}
msg.cmd = "OnInit"
msg.t = timemsec()
msg.data = script_path
sendCallback(msg)
end
log("QUIK# is initialized from "..script_path, 0)
end
function OnOrder(order)
local msg = {}
msg.t = timemsec()
msg.id = nil
msg.data = order
msg.cmd = "OnOrder"
sendCallback(msg)
end
function OnQuote(class_code, sec_code)
if is_connected then
local msg = {}
msg.cmd = "OnQuote"
msg.t = timemsec()
local server_time = getInfoParam("SERVERTIME")
local status, ql2 = pcall(getQuoteLevel2, class_code, sec_code)
if status then
msg.data = ql2
msg.data.class_code = class_code
msg.data.sec_code = sec_code
msg.data.server_time = server_time
sendCallback(msg)
else
OnError(ql2)
end
end
end
function OnStop(s)
is_started = false
if is_connected then
local msg = {}
msg.cmd = "OnStop"
msg.t = timemsec()
msg.data = s
sendCallback(msg)
end
log("QUIK# stopped. You could keep script running when closing QUIK and the script will start automatically the next time you start QUIK", 1)
CleanUp()
-- send disconnect
return 1000
end
function OnTrade(trade)
local msg = {}
msg.t = timemsec()
msg.id = nil
msg.data = trade
msg.cmd = "OnTrade"
sendCallback(msg)
end
function OnTransReply(trans_reply)
local msg = {}
msg.t = timemsec()
msg.id = nil
msg.data = trans_reply
msg.cmd = "OnTransReply"
sendCallback(msg)
end
function OnStopOrder(stop_order)
local msg = {}
msg.t = timemsec()
msg.data = stop_order
msg.cmd = "OnStopOrder"
sendCallback(msg)
end
function OnParam(class_code, sec_code)
local msg = {}
msg.cmd = "OnParam"
msg.t = timemsec()
local dat = {}
dat.class_code = class_code
dat.sec_code = sec_code
msg.data = dat
sendCallback(msg)
end
function OnAccountBalance(acc_bal)
local msg = {}
msg.t = timemsec()
msg.data = acc_bal
msg.cmd = "OnAccountBalance"
sendCallback(msg)
end
function OnAccountPosition(acc_pos)
local msg = {}
msg.t = timemsec()
msg.data = acc_pos
msg.cmd = "OnAccountPosition"
sendCallback(msg)
end
function OnDepoLimit(dlimit)
local msg = {}
msg.t = timemsec()
msg.data = dlimit
msg.cmd = "OnDepoLimit"
sendCallback(msg)
end
function OnDepoLimitDelete(dlimit_del)
local msg = {}
msg.t = timemsec()
msg.data = dlimit_del
msg.cmd = "OnDepoLimitDelete"
sendCallback(msg)
end
function OnFirm(firm)
local msg = {}
msg.t = timemsec()
msg.data = firm
msg.cmd = "OnFirm"
sendCallback(msg)
end
function OnFuturesClientHolding(fut_pos)
local msg = {}
msg.t = timemsec()
msg.data = fut_pos
msg.cmd = "OnFuturesClientHolding"
sendCallback(msg)
end
function OnFuturesLimitChange(fut_limit)
local msg = {}
msg.t = timemsec()
msg.data = fut_limit
msg.cmd = "OnFuturesLimitChange"
sendCallback(msg)
end
function OnFuturesLimitDelete(lim_del)
local msg = {}
msg.t = timemsec()
msg.data = lim_del
msg.cmd = "OnFuturesLimitDelete"
sendCallback(msg)
end
function OnMoneyLimit(mlimit)
local msg = {}
msg.t = timemsec()
msg.data = mlimit
msg.cmd = "OnMoneyLimit"
sendCallback(msg)
end
function OnMoneyLimitDelete(mlimit_del)
local msg = {}
msg.t = timemsec()
msg.data = mlimit_del
msg.cmd = "OnMoneyLimitDelete"
sendCallback(msg)
end
return qscallbacks

940
QUIK/lua/qsfunctions.lua Normal file
View File

@@ -0,0 +1,940 @@
--~ Copyright (c) 2014-2020 QUIKSharp Authors https://github.com/finsight/QUIKSharp/blob/master/AUTHORS.md. All rights reserved.
--~ Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.
local json = require ("dkjson")
local qsfunctions = {}
function qsfunctions.dispatch_and_process(msg)
if qsfunctions[msg.cmd] then
-- dispatch a command simply by a table lookup
-- in qsfunctions method names must match commands
local status, result = pcall(qsfunctions[msg.cmd], msg)
if status then
return result
else
msg.cmd = "lua_error"
msg.lua_error = "Lua error: " .. result
return msg
end
else
log(to_json(msg), 3)
msg.lua_error = "Command not implemented in Lua qsfunctions module: " .. msg.cmd
msg.cmd = "lua_error"
return msg
end
end
---------------------
-- Debug functions --
---------------------
--- Returns Pong to Ping
-- @param msg message table
-- @return same msg table
function qsfunctions.ping(msg)
-- need to know data structure the caller gives
msg.t = 0 -- avoid time generation. Could also leave original
if msg.data == "Ping" then
msg.data = "Pong"
return msg
else
msg.data = msg.data .. " is not Ping"
return msg
end
end
--- Echoes its message
function qsfunctions.echo(msg)
return msg
end
--- Test error handling
function qsfunctions.divide_string_by_zero(msg)
--noinspection LuaDivideByZero
msg.data = "asd" / 0
return msg
end
--- Is running inside quik
function qsfunctions.is_quik(msg)
if getScriptPath then msg.data = 1 else msg.data = 0 end
return msg
end
-----------------------
-- Service functions --
-----------------------
--- Функция предназначена для определения состояния подключения клиентского места к
-- серверу. Возвращает «1», если клиентское место подключено и «0», если не подключено.
function qsfunctions.isConnected(msg)
-- set time when function was called
msg.t = timemsec()
msg.data = isConnected()
return msg
end
--- Функция возвращает путь, по которому находится файл info.exe, исполняющий данный
-- скрипт, без завершающего обратного слэша («\»). Например, C:\QuikFront.
function qsfunctions.getWorkingFolder(msg)
-- set time when function was called
msg.t = timemsec()
msg.data = getWorkingFolder()
return msg
end
--- Функция возвращает путь, по которому находится запускаемый скрипт, без завершающего
-- обратного слэша («\»). Например, C:\QuikFront\Scripts.
function qsfunctions.getScriptPath(msg)
-- set time when function was called
msg.t = timemsec()
msg.data = getScriptPath()
return msg
end
--- Функция возвращает значения параметров информационного окна (пункт меню
-- Связь / Информационное окно…).
function qsfunctions.getInfoParam(msg)
-- set time when function was called
msg.t = timemsec()
msg.data = getInfoParam(msg.data)
return msg
end
--- Функция отображает сообщения в терминале QUIK.
function qsfunctions.message(msg)
log(msg.data, 1)
msg.data = ""
return msg
end
function qsfunctions.warning_message(msg)
log(msg.data, 2)
msg.data = ""
return msg
end
function qsfunctions.error_message(msg)
log(msg.data, 3)
msg.data = ""
return msg
end
--- Функция приостанавливает выполнение скрипта.
function qsfunctions.sleep(msg)
delay(msg.data)
msg.data = ""
return msg
end
--- Функция для вывода отладочной информации.
function qsfunctions.PrintDbgStr(msg)
log(msg.data, 0)
msg.data = ""
return msg
end
-- Выводит на график метку
function qsfunctions.addLabel(msg)
local spl = split(msg.data, "|")
local price, curdate, curtime, qty, path, id, algmnt, bgnd = spl[1], spl[2], spl[3], spl[4], spl[5], spl[6], spl[7], spl[8]
local label = {
TEXT = "",
IMAGE_PATH = path,
ALIGNMENT = algmnt,
YVALUE = tostring(price),
DATE = tostring(curdate),
TIME = tostring(curtime),
R = 255,
G = 255,
B = 255,
TRANSPARENCY = 0,
TRANSPARENT_BACKGROUND = bgnd,
FONT_FACE_NAME = "Arial",
FONT_HEIGHT = "15",
HINT = " " .. tostring(price) .. " " .. tostring(qty)
}
local res = AddLabel(id, label)
msg.data = res
return msg
end
-- Удаляем выбранную метку
function qsfunctions.delLabel(msg)
local spl = split(msg.data, "|")
local tag, id = spl[1], spl[2]
DelLabel(tag, tonumber(id))
msg.data = ""
return msg
end
-- Удаляем все метки с графика
function qsfunctions.delAllLabels(msg)
local spl = split(msg.data, "|")
local id = spl[1]
DelAllLabels(id)
msg.data = ""
return msg
end
---------------------
-- Class functions --
---------------------
--- Функция предназначена для получения списка кодов классов, переданных с сервера в ходе сеанса связи.
function qsfunctions.getClassesList(msg)
msg.data = getClassesList()
-- if msg.data then log(msg.data) else log("getClassesList returned nil") end
return msg
end
--- Функция предназначена для получения информации о классе.
function qsfunctions.getClassInfo(msg)
msg.data = getClassInfo(msg.data)
-- if msg.data then log(msg.data.name) else log("getClassInfo returned nil") end
return msg
end
--- Функция предназначена для получения списка кодов бумаг для списка классов, заданного списком кодов.
function qsfunctions.getClassSecurities(msg)
msg.data = getClassSecurities(msg.data)
-- if msg.data then log(msg.data) else log("getClassSecurities returned nil") end
return msg
end
--- Функция получает информацию по указанному классу и бумаге.
function qsfunctions.getSecurityInfo(msg)
local spl = split(msg.data, "|")
local class_code, sec_code = spl[1], spl[2]
msg.data = getSecurityInfo(class_code, sec_code)
return msg
end
--- Функция берет на вход список из элементов в формате class_code|sec_code и возвращает список ответов функции getSecurityInfo.
-- Если какая-то из бумаг не будет найдена, вместо ее значения придет null
function qsfunctions.getSecurityInfoBulk(msg)
local result = {}
for i=1,#msg.data do
local spl = split(msg.data[i], "|")
local class_code, sec_code = spl[1], spl[2]
local status, security = pcall(getSecurityInfo, class_code, sec_code)
if status and security then
table.insert(result, security)
else
if not status then
log("Error happened while calling getSecurityInfoBulk with ".. class_code .. "|".. sec_code .. ": ".. security)
end
table.insert(result, json.null)
end
end
msg.data = result
return msg
end
--- Функция предназначена для определения класса по коду инструмента из заданного списка классов.
function qsfunctions.getSecurityClass(msg)
local spl = split(msg.data, "|")
local classes_list, sec_code = spl[1], spl[2]
for class_code in string.gmatch(classes_list,"([^,]+)") do
if getSecurityInfo(class_code,sec_code) then
msg.data = class_code
return msg
end
end
msg.data = ""
return msg
end
--- Функция возвращает код клиента
function qsfunctions.getClientCode(msg)
for i=0,getNumberOf("MONEY_LIMITS")-1 do
local clientcode = getItem("MONEY_LIMITS",i).client_code
if clientcode ~= nil then
msg.data = clientcode
return msg
end
end
return msg
end
--- Функция возвращает все коды клиента
function qsfunctions.getClientCodes(msg)
local client_codes = {}
for i=0,getNumberOf("MONEY_LIMITS")-1 do
local clientcode = getItem("MONEY_LIMITS",i).client_code
if clientcode ~= nil then
table.insert(client_codes, clientcode)
end
end
msg.data = client_codes
return msg
end
--- Функция возвращает торговый счет для запрашиваемого кода класса
function qsfunctions.getTradeAccount(msg)
for i=0,getNumberOf("trade_accounts")-1 do
local trade_account = getItem("trade_accounts",i)
if string.find(trade_account.class_codes,'|' .. msg.data .. '|',1,1) then
msg.data = trade_account.trdaccid
return msg
end
end
return msg
end
--- Функция возвращает торговые счета в системе, у которых указаны поддерживаемые классы инструментов.
function qsfunctions.getTradeAccounts(msg)
local trade_accounts = {}
for i=0,getNumberOf("trade_accounts")-1 do
local trade_account = getItem("trade_accounts",i)
if trade_account.class_codes ~= "" then
table.insert(trade_accounts, trade_account)
end
end
msg.data = trade_accounts
return msg
end
---------------------------------------------------------------------
-- Order Book functions (Функции для работы со стаканом котировок) --
---------------------------------------------------------------------
--- Функция заказывает на сервер получение стакана по указанному классу и бумаге.
function qsfunctions.Subscribe_Level_II_Quotes(msg)
local spl = split(msg.data, "|")
local class_code, sec_code = spl[1], spl[2]
msg.data = Subscribe_Level_II_Quotes(class_code, sec_code)
return msg
end
--- Функция отменяет заказ на получение с сервера стакана по указанному классу и бумаге.
function qsfunctions.Unsubscribe_Level_II_Quotes(msg)
local spl = split(msg.data, "|")
local class_code, sec_code = spl[1], spl[2]
msg.data = Unsubscribe_Level_II_Quotes(class_code, sec_code)
return msg
end
--- Функция позволяет узнать, заказан ли с сервера стакан по указанному классу и бумаге.
function qsfunctions.IsSubscribed_Level_II_Quotes(msg)
local spl = split(msg.data, "|")
local class_code, sec_code = spl[1], spl[2]
msg.data = IsSubscribed_Level_II_Quotes(class_code, sec_code)
return msg
end
--- Функция предназначена для получения стакана по указанному классу и инструменту.
function qsfunctions.GetQuoteLevel2(msg)
local spl = split(msg.data, "|")
local class_code, sec_code = spl[1], spl[2]
local server_time = getInfoParam("SERVERTIME")
local status, ql2 = pcall(getQuoteLevel2, class_code, sec_code)
if status then
msg.data = ql2
msg.data.class_code = class_code
msg.data.sec_code = sec_code
msg.data.server_time = server_time
else
OnError(ql2)
end
return msg
end
-----------------------
-- Trading functions --
-----------------------
--- отправляет транзакцию на сервер и возвращает пустое сообщение, которое
-- будет проигноировано. Вместо него, отправитель будет ждать события
-- OnTransReply, из которого по TRANS_ID он получит результат отправленной транзакции
function qsfunctions.sendTransaction(msg)
local res = sendTransaction(msg.data)
if res~="" then
-- error handling
msg.cmd = "lua_transaction_error"
msg.lua_error = res
return msg
else
-- transaction sent
msg.data = true
return msg
end
end
--- Функция заказывает получение параметров Таблицы текущих торгов. В случае успешного завершения функция возвращает «true», иначе «false»
function qsfunctions.paramRequest(msg)
local spl = split(msg.data, "|")
local class_code, sec_code, param_name = spl[1], spl[2], spl[3]
msg.data = ParamRequest(class_code, sec_code, param_name)
return msg
end
--- Функция принимает список строк (JSON Array) в формате class_code|sec_code|param_name, вызывает функцию paramRequest для каждой строки.
-- Возвращает список ответов в том же порядке
function qsfunctions.paramRequestBulk(msg)
local result = {}
for i=1,#msg.data do
local spl = split(msg.data[i], "|")
local class_code, sec_code, param_name = spl[1], spl[2], spl[3]
table.insert(result, ParamRequest(class_code, sec_code, param_name))
end
msg.data = result
return msg
end
--- Функция отменяет заказ на получение параметров Таблицы текущих торгов. В случае успешного завершения функция возвращает «true», иначе «false»
function qsfunctions.cancelParamRequest(msg)
local spl = split(msg.data, "|")
local class_code, sec_code, param_name = spl[1], spl[2], spl[3]
msg.data = CancelParamRequest(class_code, sec_code, param_name)
return msg
end
--- Функция принимает список строк (JSON Array) в формате class_code|sec_code|param_name, вызывает функцию CancelParamRequest для каждой строки.
-- Возвращает список ответов в том же порядке
function qsfunctions.cancelParamRequestBulk(msg)
local result = {}
for i=1,#msg.data do
local spl = split(msg.data[i], "|")
local class_code, sec_code, param_name = spl[1], spl[2], spl[3]
table.insert(result, CancelParamRequest(class_code, sec_code, param_name))
end
msg.data = result
return msg
end
--- Функция предназначена для получения значений всех параметров биржевой информации из Таблицы текущих значений параметров.
-- С помощью этой функции можно получить любое из значений Таблицы текущих значений параметров для заданных кодов класса и бумаги.
function qsfunctions.getParamEx(msg)
local spl = split(msg.data, "|")
local class_code, sec_code, param_name = spl[1], spl[2], spl[3]
msg.data = getParamEx(class_code, sec_code, param_name)
return msg
end
--- Функция предназначена для получения значении? всех параметров биржевои? информации из Таблицы текущих торгов
-- с возможностью в дальнеи?шем отказаться от получения определенных параметров, заказанных с помощью функции ParamRequest.
-- Для отказа от получения какого-либо параметра воспользуи?тесь функциеи? CancelParamRequest.
-- Функция возвращает таблицу Lua с параметрами, аналогичными параметрам, возвращаемым функциеи? getParamEx
function qsfunctions.getParamEx2(msg)
local spl = split(msg.data, "|")
local class_code, sec_code, param_name = spl[1], spl[2], spl[3]
msg.data = getParamEx2(class_code, sec_code, param_name)
return msg
end
--- Функция принимает список строк (JSON Array) в формате class_code|sec_code|param_name и возвращает результаты вызова
-- функции getParamEx2 для каждой строки запроса в виде списка в таком же порядке, как в запросе
function qsfunctions.getParamEx2Bulk(msg)
local result = {}
for i=1,#msg.data do
local spl = split(msg.data[i], "|")
local class_code, sec_code, param_name = spl[1], spl[2], spl[3]
table.insert(result, getParamEx2(class_code, sec_code, param_name))
end
msg.data = result
return msg
end
-- Функция предназначена для получения информации по бумажным лимитам.
function qsfunctions.getDepo(msg)
local spl = split(msg.data, "|")
local clientCode, firmId, secCode, account = spl[1], spl[2], spl[3], spl[4]
msg.data = getDepo(clientCode, firmId, secCode, account)
return msg
end
-- Функция предназначена для получения информации по бумажным лимитам.
function qsfunctions.getDepoEx(msg)
local spl = split(msg.data, "|")
local firmId, clientCode, secCode, account, limit_kind = spl[1], spl[2], spl[3], spl[4], spl[5]
msg.data = getDepoEx(firmId, clientCode, secCode, account, tonumber(limit_kind))
return msg
end
-- Функция для получения информации по денежным лимитам.
function qsfunctions.getMoney(msg)
local spl = split(msg.data, "|")
local client_code, firm_id, tag, curr_code = spl[1], spl[2], spl[3], spl[4]
msg.data = getMoney(client_code, firm_id, tag, curr_code)
return msg
end
-- Функция для получения информации по денежным лимитам указанного типа.
function qsfunctions.getMoneyEx(msg)
local spl = split(msg.data, "|")
local firm_id, client_code, tag, curr_code, limit_kind = spl[1], spl[2], spl[3], spl[4], spl[5]
msg.data = getMoneyEx(firm_id, client_code, tag, curr_code, tonumber(limit_kind))
return msg
end
-- Функция возвращает информацию по всем денежным лимитам.
function qsfunctions.getMoneyLimits(msg)
local limits = {}
for i=0,getNumberOf("money_limits")-1 do
local limit = getItem("money_limits",i)
table.insert(limits, limit)
end
msg.data = limits
return msg
end
-- Функция предназначена для получения информации по фьючерсным лимитам.
function qsfunctions.getFuturesLimit(msg)
local spl = split(msg.data, "|")
local firmId, accId, limitType, currCode = spl[1], spl[2], spl[3], spl[4]
local result, err = getFuturesLimit(firmId, accId, limitType*1, currCode)
if result then
msg.data = result
else
log("Futures limit returns nil", 3)
msg.data = nil
end
return msg
end
-- Функция возвращает информацию по фьючерсным лимитам для всех торговых счетов.
function qsfunctions.getFuturesClientLimits(msg)
local limits = {}
for i=0,getNumberOf("futures_client_limits")-1 do
local limit = getItem("futures_client_limits",i)
table.insert(limits, limit)
end
msg.data = limits
return msg
end
--- (ichechet) Через getFuturesHolding позиции не приходили. Пришлось сделать обработку таблицы futures_client_holding
function qsfunctions.getFuturesHolding(msg)
if msg.data ~= "" then
local spl = split(msg.data, "|")
local firmId, accId, secCode, posType = spl[1], spl[2], spl[3], spl[4]
end
local fchs = {}
for i = 0, getNumberOf("futures_client_holding") - 1 do
local fch = getItem("futures_client_holding", i)
if msg.data == "" or (fch.firmid == firmId and fch.trdaccid == accId and fch.sec_code == secCode and fch.type == posType*1) then
table.insert(fchs, fch)
end
end
msg.data = fchs
return msg
end
-- Функция возвращает таблицу заявок (всю или по заданному инструменту)
function qsfunctions.get_orders(msg)
if msg.data ~= "" then
local spl = split(msg.data, "|")
local class_code, sec_code = spl[1], spl[2]
end
local orders = {}
for i = 0, getNumberOf("orders") - 1 do
local order = getItem("orders", i)
if msg.data == "" or (order.class_code == class_code and order.sec_code == sec_code) then
table.insert(orders, order)
end
end
msg.data = orders
return msg
end
-- Функция возвращает заявку по заданному инструменту и ID-транзакции
function qsfunctions.getOrder_by_ID(msg)
if msg.data ~= "" then
local spl = split(msg.data, "|")
local class_code, sec_code, trans_id = spl[1], spl[2], spl[3]
end
local order_num = 0
local res
for i = 0, getNumberOf("orders") - 1 do
local order = getItem("orders", i)
if order.class_code == class_code and order.sec_code == sec_code and order.trans_id == tonumber(trans_id) and order.order_num > order_num then
order_num = order.order_num
res = order
end
end
msg.data = res
return msg
end
---- Функция возвращает заявку по номеру
function qsfunctions.getOrder_by_Number(msg)
for i=0,getNumberOf("orders")-1 do
local order = getItem("orders",i)
if order.order_num == tonumber(msg.data) then
msg.data = order
return msg
end
end
return msg
end
--- Возвращает заявку по её номеру и классу инструмента ---
--- На основе http://help.qlua.org/ch4_5_1_1.htm ---
function qsfunctions.get_order_by_number(msg)
local spl = split(msg.data, "|")
local class_code = spl[1]
local order_id = tonumber(spl[2])
msg.data = getOrderByNumber(class_code, order_id)
return msg
end
--- Возвращает список записей из таблицы 'Лимиты по бумагам'
--- На основе http://help.qlua.org/ch4_6_11.htm и http://help.qlua.org/ch4_5_3.htm
function qsfunctions.get_depo_limits(msg)
local sec_code = msg.data
local count = getNumberOf("depo_limits")
local depo_limits = {}
for i = 0, count - 1 do
local depo_limit = getItem("depo_limits", i)
if msg.data == "" or depo_limit.sec_code == sec_code then
table.insert(depo_limits, depo_limit)
end
end
msg.data = depo_limits
return msg
end
-- Функция возвращает таблицу сделок (всю или по заданному инструменту)
function qsfunctions.get_trades(msg)
if msg.data ~= "" then
local spl = split(msg.data, "|")
local class_code, sec_code = spl[1], spl[2]
end
local trades = {}
for i = 0, getNumberOf("trades") - 1 do
local trade = getItem("trades", i)
if msg.data == "" or (trade.class_code == class_code and trade.sec_code == sec_code) then
table.insert(trades, trade)
end
end
msg.data = trades
return msg
end
-- Функция возвращает таблицу сделок по номеру заявки
function qsfunctions.get_Trades_by_OrderNumber(msg)
local order_num = tonumber(msg.data)
local trades = {}
for i = 0, getNumberOf("trades") - 1 do
local trade = getItem("trades", i)
if trade.order_num == order_num then
table.insert(trades, trade)
end
end
msg.data = trades
return msg
end
-- Функция предназначена для получения значений параметров таблицы «Клиентский портфель», соответствующих идентификатору участника торгов «firmid» и коду клиента «client_code».
function qsfunctions.getPortfolioInfo(msg)
local spl = split(msg.data, "|")
local firmId, clientCode = spl[1], spl[2]
msg.data = getPortfolioInfo(firmId, clientCode)
return msg
end
-- Функция предназначена для получения значений параметров таблицы «Клиентский портфель», соответствующих идентификатору участника торгов «firmid», коду клиента «client_code» и виду лимита «limit_kind».
function qsfunctions.getPortfolioInfoEx(msg)
local spl = split(msg.data, "|")
local firmId, clientCode, limit_kind = spl[1], spl[2], spl[3]
msg.data = getPortfolioInfoEx(firmId, clientCode, tonumber(limit_kind))
return msg
end
-- Функция предназначена для получения таблицы обезличенных сделок по выбранному инструменту или всю целиком.
function qsfunctions.get_all_trades(msg)
if msg.data ~= "" then
local spl = split(msg.data, "|")
local class_code, sec_code = spl[1], spl[2]
end
local trades = {}
for i = 0, getNumberOf("all_trades") - 1 do
local trade = getItem("all_trades", i)
if msg.data == "" or (trade.class_code == class_code and trade.sec_code == sec_code) then
table.insert(trades, trade)
end
end
msg.data = trades
return msg
end
--------------------------
-- OptionBoard functions --
--------------------------
function qsfunctions.getOptionBoard(msg)
local spl = split(msg.data, "|")
local classCode, secCode = spl[1], spl[2]
local result, err = getOptions(classCode, secCode)
if result then
msg.data = result
else
log("Option board returns nil", 3)
msg.data = nil
end
return msg
end
function getOptions(classCode,secCode)
--classCode = "SPBOPT"
--BaseSecList="RIZ6"
local SecList = getClassSecurities(classCode) --все сразу
local t={}
local p={}
for sec in string.gmatch(SecList, "([^,]+)") do --перебираем опционы по очереди.
local Optionbase=getParamEx(classCode,sec,"optionbase").param_image
local Optiontype=getParamEx(classCode,sec,"optiontype").param_image
if (string.find(secCode,Optionbase)~=nil) then
p={
["code"]=getParamEx(classCode,sec,"code").param_image,
["Name"]=getSecurityInfo(classCode,sec).name,
["DAYS_TO_MAT_DATE"]=getParamEx(classCode,sec,"DAYS_TO_MAT_DATE").param_value+0,
["BID"]=getParamEx(classCode,sec,"BID").param_value+0,
["OFFER"]=getParamEx(classCode,sec,"OFFER").param_value+0,
["OPTIONBASE"]=getParamEx(classCode,sec,"optionbase").param_image,
["OPTIONTYPE"]=getParamEx(classCode,sec,"optiontype").param_image,
["Longname"]=getParamEx(classCode,sec,"longname").param_image,
["shortname"]=getParamEx(classCode,sec,"shortname").param_image,
["Volatility"]=getParamEx(classCode,sec,"volatility").param_value+0,
["Strike"]=getParamEx(classCode,sec,"strike").param_value+0
}
table.insert( t, p )
end
end
return t
end
--------------------------
-- Stop order functions --
--------------------------
--- Возвращает список стоп-заявок
function qsfunctions.get_stop_orders(msg)
if msg.data ~= "" then
local spl = split(msg.data, "|")
local class_code, sec_code = spl[1], spl[2]
end
local count = getNumberOf("stop_orders")
local stop_orders = {}
for i = 0, count - 1 do
local stop_order = getItem("stop_orders", i)
if msg.data == "" or (stop_order.class_code == class_code and stop_order.sec_code == sec_code) then
table.insert(stop_orders, stop_order)
end
end
msg.data = stop_orders
return msg
end
-------------------------
--- Candles functions ---
-------------------------
--- Возвращаем количество свечей по тегу
function qsfunctions.get_num_candles(msg)
log("Called get_num_candles" .. msg.data, 2)
local spl = split(msg.data, "|")
local tag = spl[1]
msg.data = getNumCandles(tag) * 1
return msg
end
--- Возвращаем все свечи по идентификатору графика. График должен быть открыт
function qsfunctions.get_candles(msg)
log("Called get_candles" .. msg.data, 2)
local spl = split(msg.data, "|")
local tag = spl[1]
local line = tonumber(spl[2])
local first_candle = tonumber(spl[3])
local count = tonumber(spl[4])
if count == 0 then
count = getNumCandles(tag) * 1
end
log("Count: " .. count, 2)
local t,n,l = getCandlesByIndex(tag, line, first_candle, count)
log("Candles table size: " .. n, 2)
log("Label: " .. l, 2)
local candles = {}
for i = 0, count - 1 do
table.insert(candles, t[i])
end
msg.data = candles
return msg
end
--- Возвращаем все свечи по заданному инструменту и интервалу
--- (ichechet) Если исторические данные по тикеру не приходят, то QUIK блокируется. Чтобы это не происходило, вводим таймаут
function qsfunctions.get_candles_from_data_source(msg)
local ds, is_error = create_data_source(msg)
if not is_error then
--- Источник данных изначально приходит пустым. Нужно подождать пока он заполнится данными. Бесконечно ждать тоже нельзя. Вводим таймаут
local s = 0 --- Будем ждать 5 секунд, прежде чем вернем таймаут
repeat --- Ждем
sleep(100) --- 100 миллисекунд
s = s + 100 --- Запоминаем кол-во прошедших миллисекунд
until (ds:Size() > 0 or s > 5000) --- До тех пор, пока не придут данные или пока не наступит таймаут
local count = tonumber(split(msg.data, "|")[4]) --- возвращаем последние count свечей. Если равен 0, то возвращаем все доступные свечи.
local class, sec, interval = get_candles_param(msg)
local candles = {}
local start_i = count == 0 and 1 or math.max(1, ds:Size() - count + 1)
for i = start_i, ds:Size() do
local candle = fetch_candle(ds, i)
candle.sec = sec
candle.class = class
candle.interval = interval
table.insert(candles, candle)
end
ds:Close()
msg.data = candles
end
return msg
end
function create_data_source(msg)
local class, sec, interval = get_candles_param(msg)
local ds, error_descr = CreateDataSource(class, sec, interval)
local is_error = false
if(error_descr ~= nil) then
msg.cmd = "lua_create_data_source_error"
msg.lua_error = error_descr
is_error = true
elseif ds == nil then
msg.cmd = "lua_create_data_source_error"
msg.lua_error = "Can't create data source for " .. class .. ", " .. sec .. ", " .. tostring(interval)
is_error = true
end
return ds, is_error
end
function fetch_candle(data_source, index)
local candle = {}
candle.low = data_source:L(index)
candle.close = data_source:C(index)
candle.high = data_source:H(index)
candle.open = data_source:O(index)
candle.volume = data_source:V(index)
candle.datetime = data_source:T(index)
return candle
end
--- Словарь открытых подписок (datasources) на свечи
data_sources = {}
last_indexes = {}
--- Подписаться на получения свечей по заданному инструмент и интервалу
function qsfunctions.subscribe_to_candles(msg)
local ds, is_error = create_data_source(msg)
if not is_error then
local class, sec, interval = get_candles_param(msg)
local key = get_key(class, sec, interval)
data_sources[key] = ds
last_indexes[key] = ds:Size()
ds:SetUpdateCallback(
function(index)
data_source_callback(index, class, sec, interval)
end)
end
return msg
end
function data_source_callback(index, class, sec, interval)
local key = get_key(class, sec, interval)
if index ~= last_indexes[key] then
last_indexes[key] = index
local candle = fetch_candle(data_sources[key], index - 1)
candle.sec = sec
candle.class = class
candle.interval = interval
local msg = {}
msg.t = timemsec()
msg.cmd = "NewCandle"
msg.data = candle
sendCallback(msg)
end
end
--- Отписать от получения свечей по заданному инструменту и интервалу
function qsfunctions.unsubscribe_from_candles(msg)
local class, sec, interval = get_candles_param(msg)
local key = get_key(class, sec, interval)
data_sources[key]:Close()
data_sources[key] = nil
last_indexes[key] = nil
return msg
end
--- Проверить открыта ли подписка на заданный инструмент и интервал
function qsfunctions.is_subscribed(msg)
local class, sec, interval = get_candles_param(msg)
local key = get_key(class, sec, interval)
for k, v in pairs(data_sources) do
if key == k then
msg.data = true;
return msg
end
end
msg.data = false
return msg
end
--- Возвращает из msg информацию о инструменте на который подписываемся и интервале
function get_candles_param(msg)
local spl = split(msg.data, "|")
return spl[1], spl[2], tonumber(spl[3])
end
--- Возвращает уникальный ключ для инструмента на который подписываемся и инетрвала
function get_key(class, sec, interval)
return class .. "|" .. sec .. "|" .. tostring(interval)
end
-------------------------
--- UCP functions ---
-------------------------
--- Функция возвращает торговый счет срочного рынка, соответствующий коду клиента фондового рынка с единой денежной позицией
function qsfunctions.GetTrdAccByClientCode(msg)
local spl = split(msg.data, "|")
local firmId, clientCode = spl[1], spl[2]
msg.data = getTrdAccByClientCode(firmId, clientCode)
return msg
end
--- Функция возвращает код клиента фондового рынка с единой денежной позицией, соответствующий торговому счету срочного рынка
function qsfunctions.GetClientCodeByTrdAcc(msg)
local spl = split(msg.data, "|")
local firmId, trdAccId = spl[1], spl[2]
msg.data = getClientCodeByTrdAcc(firmId, trdAccId)
return msg
end
--- Функция предназначена для получения признака, указывающего имеет ли клиент единую денежную позицию
function qsfunctions.IsUcpClient(msg)
local spl = split(msg.data, "|")
local firmId, client = spl[1], spl[2]
msg.data = isUcpClient(firmId, client)
return msg
end
return qsfunctions

295
QUIK/lua/qsutils.lua Normal file
View File

@@ -0,0 +1,295 @@
--~ Copyright (c) 2014-2020 QUIKSharp Authors https://github.com/finsight/QUIKSharp/blob/master/AUTHORS.md. All rights reserved.
--~ Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.
local socket = require ("socket")
local json = require ("dkjson")
local qsutils = {}
--- Sleep that always works
function delay(msec)
if sleep then
pcall(sleep, msec)
else
-- pcall(socket.select, nil, nil, msec / 1000)
end
end
-- high precision current time
function timemsec()
local st, res = pcall(socket.gettime)
if st then
return (res) * 1000
else
log("unexpected error in timemsec", 3)
error("unexpected error in timemsec")
end
end
-- Returns the name of the file that calls this function (without extension)
function scriptFilename()
-- Check that Lua runtime was built with debug information enabled
if not debug or not debug.getinfo then
return nil
end
local full_path = debug.getinfo(2, "S").source:sub(2)
return string.gsub(full_path, "^.*\\(.*)[.]lua[c]?$", "%1")
end
-- when true will show QUIK message for log(...,0)
is_debug = false
-- log files
function openLog()
os.execute("mkdir \""..script_path.."\\logs\"")
local lf = io.open (script_path.. "\\logs\\QUIK#_"..os.date("%Y%m%d")..".log", "a")
if not lf then
lf = io.open (script_path.. "\\QUIK#_"..os.date("%Y%m%d")..".log", "a")
end
return lf
end
-- Returns contents of config.json file or nil if no such file exists
function readConfigAsJson()
local conf = io.open (script_path.. "\\config.json", "r")
if not conf then
return nil
end
local content = conf:read "*a"
conf:close()
return from_json(content)
end
function paramsFromConfig(scriptName)
local params = {}
-- just default values
table.insert(params, "127.0.0.1") -- responseHostname
table.insert(params, 34130) -- responsePort
table.insert(params, "127.0.0.1") -- callbackHostname
table.insert(params, 34131) -- callbackPort
local config = readConfigAsJson()
if not config or not config.servers then
return nil
end
local found = false
for i=1,#config.servers do
local server = config.servers[i]
if server.scriptName == scriptName then
found = true
if server.responseHostname then
params[1] = server.responseHostname
end
if server.responsePort then
params[2] = server.responsePort
end
if server.callbackHostname then
params[3] = server.callbackHostname
end
if server.callbackPort then
params[4] = server.callbackPort
end
end
end
if found then
return params
else
return nil
end
end
-- closes log
function closeLog()
if logfile then
pcall(logfile:close(logfile))
end
end
logfile = openLog()
--- Write to log file and to Quik messages
function log(msg, level)
if not msg then msg = "" end
if level == 1 or level == 2 or level == 3 or is_debug then
-- only warnings and recoverable errors to Quik
if message then
pcall(message, msg, level)
end
end
if not level then level = 0 end
local logLine = "LOG "..level..": "..msg
print(logLine)
local msecs = math.floor(math.fmod(timemsec(), 1000));
if logfile then
pcall(logfile.write, logfile, os.date("%Y-%m-%d %H:%M:%S").."."..msecs.." "..logLine.."\n")
pcall(logfile.flush, logfile)
end
end
function split(inputstr, sep)
if sep == nil then
sep = "%s"
end
local t={}
local i=1
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
t[i] = str
i = i + 1
end
return t
end
function from_json(str)
local status, msg= pcall(json.decode, str, 1, json.null) -- dkjson
if status then
return msg
else
return nil, msg
end
end
function to_json(msg)
local status, str= pcall(json.encode, msg, { indent = false }) -- dkjson
if status then
return str
else
error(str)
end
end
-- current connection state
is_connected = false
local response_server
local callback_server
local response_client
local callback_client
--- accept client on server
local function getResponseServer()
log('Waiting for a response client...', 0)
if not response_server then
log("Cannot bind to response_server, probably the port is already in use", 3)
else
while true do
local status, client, err = pcall(response_server.accept, response_server )
if status and client then
return client
else
log(err, 3)
end
end
end
end
local function getCallbackClient()
log('Waiting for a callback client...', 0)
if not callback_server then
log("Cannot bind to callback_server, probably the port is already in use", 3)
else
while true do
local status, client, err = pcall(callback_server.accept, callback_server)
if status and client then
return client
else
log(err, 3)
end
end
end
end
function qsutils.connect(response_host, response_port, callback_host, callback_port)
if not response_server then
response_server = socket.bind(response_host, response_port, 1)
end
if not callback_server then
callback_server = socket.bind(callback_host, callback_port, 1)
end
if not is_connected then
log('QUIK# is waiting for client connection...', 1)
if response_client then
log("is_connected is false but the response client is not nil", 3)
-- Quik crashes without pcall
pcall(response_client.close, response_client)
end
if callback_client then
log("is_connected is false but the callback client is not nil", 3)
-- Quik crashes without pcall
pcall(callback_client.close, callback_client)
end
response_client = getResponseServer()
callback_client = getCallbackClient()
if response_client and callback_client then
is_connected = true
log('QUIK# client connected', 1)
end
end
end
local function disconnected()
is_connected = false
log('QUIK# client disconnected', 1)
if response_client then
pcall(response_client.close, response_client)
response_client = nil
end
if callback_client then
pcall(callback_client.close, callback_client)
callback_client = nil
end
OnQuikSharpDisconnected()
end
--- get a decoded message as a table
function receiveRequest()
if not is_connected then
return nil, "not conencted"
end
local status, requestString= pcall(response_client.receive, response_client)
if status and requestString then
local msg_table, err = from_json(requestString)
if err then
log(err, 3)
return nil, err
else
return msg_table
end
else
disconnected()
return nil, err
end
end
function sendResponse(msg_table)
-- if not set explicitly then set CreatedTime "t" property here
-- if not msg_table.t then msg_table.t = timemsec() end
local responseString = to_json(msg_table)
if is_connected then
local status, res = pcall(response_client.send, response_client, responseString..'\n')
if status and res then
return true
else
disconnected()
return nil, err
end
end
end
function sendCallback(msg_table)
-- if not set explicitly then set CreatedTime "t" property here
-- if not msg_table.t then msg_table.t = timemsec() end
local callback_string = to_json(msg_table)
if is_connected then
local status, res = pcall(callback_client.send, callback_client, callback_string..'\n')
if status and res then
return true
else
disconnected()
return nil, err
end
end
end
return qsutils

149
QUIK/lua/socket.lua Normal file
View File

@@ -0,0 +1,149 @@
-----------------------------------------------------------------------------
-- LuaSocket helper module
-- Author: Diego Nehab
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Declare module and import dependencies
-----------------------------------------------------------------------------
local base = _G
local string = require("string")
local math = require("math")
local socket = require("socket.core")
local _M = socket
-----------------------------------------------------------------------------
-- Exported auxiliar functions
-----------------------------------------------------------------------------
function _M.connect4(address, port, laddress, lport)
return socket.connect(address, port, laddress, lport, "inet")
end
function _M.connect6(address, port, laddress, lport)
return socket.connect(address, port, laddress, lport, "inet6")
end
function _M.bind(host, port, backlog)
if host == "*" then host = "0.0.0.0" end
local addrinfo, err = socket.dns.getaddrinfo(host);
if not addrinfo then return nil, err end
local sock, res
err = "no info on address"
for i, alt in base.ipairs(addrinfo) do
if alt.family == "inet" then
sock, err = socket.tcp4()
else
sock, err = socket.tcp6()
end
if not sock then return nil, err end
sock:setoption("reuseaddr", true)
res, err = sock:bind(alt.addr, port)
if not res then
sock:close()
else
res, err = sock:listen(backlog)
if not res then
sock:close()
else
return sock
end
end
end
return nil, err
end
_M.try = _M.newtry()
function _M.choose(table)
return function(name, opt1, opt2)
if base.type(name) ~= "string" then
name, opt1, opt2 = "default", name, opt1
end
local f = table[name or "nil"]
if not f then base.error("unknown key (".. base.tostring(name) ..")", 3)
else return f(opt1, opt2) end
end
end
-----------------------------------------------------------------------------
-- Socket sources and sinks, conforming to LTN12
-----------------------------------------------------------------------------
-- create namespaces inside LuaSocket namespace
local sourcet, sinkt = {}, {}
_M.sourcet = sourcet
_M.sinkt = sinkt
_M.BLOCKSIZE = 2048
sinkt["close-when-done"] = function(sock)
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function(self, chunk, err)
if not chunk then
sock:close()
return 1
else return sock:send(chunk) end
end
})
end
sinkt["keep-open"] = function(sock)
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function(self, chunk, err)
if chunk then return sock:send(chunk)
else return 1 end
end
})
end
sinkt["default"] = sinkt["keep-open"]
_M.sink = _M.choose(sinkt)
sourcet["by-length"] = function(sock, length)
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function()
if length <= 0 then return nil end
local size = math.min(socket.BLOCKSIZE, length)
local chunk, err = sock:receive(size)
if err then return nil, err end
length = length - string.len(chunk)
return chunk
end
})
end
sourcet["until-closed"] = function(sock)
local done
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function()
if done then return nil end
local chunk, err, partial = sock:receive(socket.BLOCKSIZE)
if not err then return chunk
elseif err == "closed" then
sock:close()
done = 1
return partial
else return nil, err end
end
})
end
sourcet["default"] = sourcet["until-closed"]
_M.source = _M.choose(sourcet)
return _M

BIN
QUIK/socket/core.dll Normal file

Binary file not shown.