Initial commit
This commit is contained in:
140
QUIK/lua/QuikSharp.lua
Normal file
140
QUIK/lua/QuikSharp.lua
Normal 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
15
QUIK/lua/Quik_2.lua
Normal 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
18
QUIK/lua/config.json
Normal 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
714
QUIK/lua/dkjson.lua
Normal 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
235
QUIK/lua/qscallbacks.lua
Normal 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
940
QUIK/lua/qsfunctions.lua
Normal 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
295
QUIK/lua/qsutils.lua
Normal 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
149
QUIK/lua/socket.lua
Normal 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
BIN
QUIK/socket/core.dll
Normal file
Binary file not shown.
Reference in New Issue
Block a user