База скрипта в QLua (lua)

Автор записи: Дмитрий (Admin)
1 звезда2 звезды3 звезды4 звезды5 звезд (Голосов 7, среднее: 5,00 из 5)
Загрузка...

-- Это основная функция скрипта, которая работает в отдельном от QUIK потоке,
-- это означает, что если QUIK будет перегружен работой,
-- например, при большом объеме поступающих сделок,
-- то QLua - скрипт продолжит выполнять свою работу в штатном режиме,
-- при условии, что у QUIK найдется процессорное время для того, чтобы "выдать" скрипту
-- необходимую информацию для работы (если она ему потребуется)
function main()
   -- здесь будет Ваш код
end;
-- Если запустить такой скрипт, то эта функция выполнится 1 раз и скрипт остановится.

-- Если Вам нужно постоянно выполнять какой-то код,
-- то для этого нужно внутри этой функции расположить т.н. "бесконечный цикл",
-- но для того, чтобы возможность остановить скрипт все-таки была,
-- делается подобным образом:
 
IsRun = true;
 
function main()
   while IsRun do
 
   -- здесь будет Ваш код
 
   sleep(1); 
   -- ОБЯЗАТЕЛЬНО !!!(это пауза в 1 миллисекунду), ИНАЧЕ СКРИПТ БУДЕТ СИЛЬНО НАГРУЖАТЬ ПРОЦЕССОР
   -- И ПРИ ЗАВЕРШЕНИИ ТАКОГО СКРИПТА QUIK МОЖЕТ АВАРИЙНО ЗАВЕРШИТЬ СВОЮ РАБОТУ
   end;
end;
 
function OnStop()
   IsRun = false;
end;
 
-- Функция OnStop вызывается автоматически, когда пользователь нажимает кнопку "Остановить",
-- или закрывает терминал.
-- Внутри этой функции можно расположить какой-то свой код,
-- который нужно выполнить по завершению работы скрипта.
-- В данном случае при завершении скрипта переменной IsRun присваивается ЛОЖЬ,
-- после чего цикл while внутри функции main перестает выполнятся и скрипт останавливается.
 
-- ВАЖНО!!! Не помещайте в эту функцию код, которому нужно много времени для выполнения,
-- потому что, время выполнения этой функции ограничено примерно 5-ю секундами.
-- Так же, как OnStop, существует функция инициализации Init, 
-- которая, в отличие от OnStop, выполняется до запуска скрипта,
-- перед началом работы функции main
 
IsRun = true;
 
function OnInit()
   -- Здесь будет Ваш код для начальной инициализации
end;
 
function main()
   while IsRun do
 
   -- Здесь будет Ваш код,
   -- который будет выполнятся,
   -- пока скрипт не остановлен
 
   sleep(1);
   end;
end;
 
function OnStop()
   -- Здесь будет Ваш код,
   -- который нужно выполнить
   -- перед остановкой скрипта
   IsRun = false;
end;
Сказочка про работу скрипта 🙂
Если у Вас появились какие-то вопросы, задайте их в комментариях под статьей !!!

Добавить комментарий

База скрипта в QLua (lua): 54 комментария

  1. Привет, а в 8 квике функция main автоматически запускается в отдельном потоке?
    в 7 квике не пробовал, в 8 запустил вот такой скрипт, и квик насмерть завис

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
    -- база скрипта
     
    IsRun = true;
     
    function OnInit()
    	-- Здесь будет Ваш код для начальной инициализации
    	PrintDbgStr('ИНИЦИАЛИЗАЦИЯ СКРИПТА')
    end;
     
    function main()
        while IsRun do
     
    		-- Здесь будет Ваш код,
    		-- который будет выполнятся,
    		-- пока скрипт не остановлен
     
    		sleep(10); --
    		PrintDbgStr("идет выполнение скрипта " ..getScriptPath())
     
        end;
    end;
     
    function OnStop()
       -- Здесь будет Ваш код,
       -- который нужно выполнить
       -- перед остановкой скрипта
       IsRun = false;
    	PrintDbgStr( 'ОСТАНОВКА СКРИПТА!!!!')
    end;
     
    OnInit()
    main()
    OnStop()
    1. Квик запустит main сам, вот как надо:
      результаты выполнения можно посмотреть в бесплатной программке DebugView

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      
      IsRun = true;
       
      function OnInit()
      	PrintDbgStr('Начало выполнения скрипта')
      end;
       
      function main()
      	while IsRun do
      		sleep(1000); --
      		PrintDbgStr("Выполняется скрипт " ..getScriptPath())
      	end;
      end;
       
      function OnStop()
      	IsRun = false;
      	PrintDbgStr( 'окончание выпонения скрипта')
      end;
  2. Всем привет! Помогите, кто может!? Хочу поставить условие для округления на количество знаков после запятой в зависимости от шага цены инструмента. А при условии скрипт выдаёт пустую таблицу и останавливается.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    for i = 1, 5 do
    	if DS[i] and DS[i]:Size() > 0  then
    		local step = getParamEx(CLASS_CODE, SEC_CODE[i],"SEC_PRICE_STEP").param_value -- получаем шаг цены
    		--среднее за пять закрытых дней
    		local SUM = 0 -- переменная суммы
    		for s = (DS[i]:Size()-1), (DS[i]:Size()-5), -1 do -- перебираем свечки из списка источников
    			SUM = SUM + (DS[i]:H(s)-DS[i]:L(s)) -- суммируем результаты (high-low)
    		end
    			local v = SUM/5 -- находим среднее значение за пять свечек
    			local v_perc = v/DS[i]:C(DS[i]:Size()-1)*100 -- считаем % от close последней свечки
     
    	-- БЕЗ УСЛОВИЯ ШАГА ЦЕНЫ ОКРУГЛЕНИЕ РАБОТАЕТ, А С УСЛОВИЕМ НЕТ
     
    		if step < 1 then -- если шаг цены меньше 1 то два знака после запятой
    			SetCell(table, 1, i, tostring(math.round(v, 2)).." (п.)")
    		else -- иначе целое число
    			SetCell(table, 1, i, tostring(math.round(v)).." (п.)")
    		end
    		SetCell(table, 2,i, tostring(math.round(v_perc, 2)).." (%)") -- выводим и округляем проценты
    	end
    end
    function math.round(num, idp)
      local mult = 10^(idp or 0)
      return math.floor(num * mult + 0.5) / mult
    end
  3. Добрый вечер! Подскажите, из-за чего нет данных?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    CLASS_CODE			= "SPBFUT";
    SEC_CODE			= "RIH0";
    INTERVAL          	= INTERVAL_D1;
    DS               	 = nil;
    IsRun = true;
    function OnInit()
       local Error = '';
       DS,Error = CreateDataSource(CLASS_CODE, SEC_CODE, INTERVAL);
       while (Error == "" or Error == nil) and DS:Size() == 0 do sleep(1) end
       if Error ~= "" and Error ~= nil then message("Ошибка подключения к графику: "..Error) return end
       DS:SetEmptyCallback()
    end
    function main()
    	while IsRun do
     
    		a  = DS:C(1)
    		message (a)
          sleep(100);
       end;
    end;
    function OnStop()
       IsRun = false;
    end;
    1. Поздравляю, вы изобрели отличный способ убить QUIK. Нельзя бесконечно ожидать чего-то в OnInit() потому, что для того, чтобы выше ожидание закончилось и DataSource начал получать данные, квику нужно выйти из OnInit() и продолжить свое собственное исполнение. Вообще, подобные конструкции не пишите НИКОГДА:

      1
      
      while (Error == "" or Error == nil) and DS:Size() == 0 do sleep(1) end

      лучше организовать ваш скрипт следующим образом:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      
      CLASS_CODE  = "SPBFUT"
      SEC_CODE    = "RIH0"
      INTERVAL    = INTERVAL_D1
      DS          = nil
       
      function OnInit()
          local Error = '';
          DS, Error = CreateDataSource(CLASS_CODE, SEC_CODE, INTERVAL);
          if Error and Error ~= "" then 
              message("Ошибка подключения к графику: " .. tostring(Error), 2)
              DS = nil
          else     
              DS:SetEmptyCallback()
          end    
      end
       
      function main()
          local i = 1
      	while not script_terminated do
              if DS and DS:Size() > 0 then
                  if i < DS:Size() then 
                      message(tostring(DS:C(i)), 1)
                      i = i + 1
                  end    
              end
              sleep(100);
          end;
      end
       
      function OnStop()
          script_terminated = true;
      end
          1. В итоге я сделал все индикаторы, которые хотел! Потестировал их! У меня их набралось уже несколько десятков. Но в итоге никакой устойчивой закономерности не нашёл. Либо, если учесть все возможные условия - то КВИК умрёт!))) А при смене графика инструмента нужно будет ждать следующей торговой сессии. Теперь хочу сделать скрипт в виде таблички для расчёта волатильности для нужных инструментов, чтобы не открывать кучу графиков для индикаторов.

            1. это лишь подтверждает мою теорию. 🙂 если у вас "квик умирает", значит это вы что-то делаете неправильно, а не квик плохой. когда вы начнете это понимать, тогда, можно сказать, что самый начальный этап обучения прошел.

              1. Скорее, сначала умру я, а квик будет жить)))! Теперь вот с Вашей помощью, за что очень благодарен, научился разделять процессы обработки свечей в индикаторах. Дельная вещь. При правильном подходе здорово работает.
                Сейчас вот сижу и голову ломаю. Давно хочу сделать High-Low уровни сегодня за вчера (то есть от начала сессии вчера до конца сессии вчера) без учёта сегодня. Сегодняшние нахожу так:

                 
                		local dt = T(indx)
                		if dt.hour == 10 and dt.min == 00 then
                			open_high = H(indx)
                			open_low = L(indx)
                		end
                		if open_high ~= nil and open_low ~= nil then
                			if H(indx) > open_high then
                				open_high = H(indx)
                			end
                			if L(indx) < open_low then
                				open_low = L(indx)
                			end
                		end
                 [/LUA
                а вот как сделать, чтобы 10.00 за вчера? может как-то из таблицы даты и времени ( dt.day(-1))?
                1. расчет high/low по дням

                  1
                  2
                  3
                  4
                  5
                  6
                  7
                  8
                  9
                  10
                  
                  minmax_by_day = {}
                  function OnCalculate(indx)
                      local dt = T(indx)
                      local current_date = dt.year * 10000 + dt.month * 100 + dt.day
                      local minmax  = minmax_by_day[current_date] or {high = H(indx), low = L(indx)}
                      minmax.high = math.max(minmax.high, H(indx))
                      minmax.low = math.min(minmax.low, L(indx))
                      minmax_by_day[current_date] = minmax
                      -- дальше ваш код
                  end

                  вы получаете таблицу minmax_by_day примерно такого содержимого

                  1
                  2
                  3
                  4
                  5
                  6
                  7
                  
                  {
                   [20200129]={low=156420,high=158300},
                   [20200204]={low=152100,high=155300},
                   [20200130]={low=154030,high=157720},
                   [20200203]={low=151030,high=153490},
                   [20200131]={low=151400,high=157920}
                  }

                  дальше можете получать minmax за день вызывая что-то вроде: x = minmax_by_day[20200203].low; вот только вычислять предыдущий день из формата 20200203 не очень удобно, лучше переводить T(indx) в unixtime. как работать с unixtime погуглите в интернете.

                  1. вот это интересный способ! Только я вот не до конца понимаю (current_date = dt.year * 10000 + dt.month * 100 + dt.day). Или это какой-то максимальный параметр? Почему год на 10000, месяц на 100, а день без умножения? Это получается как захват диапазона данных? А по скорости? что быстрее? перебор значений или math.max ?
                    А вот minmax_by_day[current_date] = minmax - это переход на следующий день?

                  2. Добрый день! С unixtime не нашёл способа применить, поэтому сделал так:

                    1
                    2
                    3
                    4
                    5
                    6
                    7
                    8
                    9
                    10
                    11
                    12
                    13
                    14
                    15
                    16
                    17
                    18
                    19
                    20
                    21
                    22
                    23
                    24
                    25
                    26
                    27
                    28
                    29
                    30
                    31
                    32
                    33
                    34
                    35
                    36
                    
                    		if indx==1 then
                    			DelAllLabels( Settings.chart );
                    			time_server = GetServerDateTime()
                    			month_server = time_server.month
                    			return nil
                    		end
                    		local dt = T(indx)
                    		local dt1 = T(indx-1)
                     
                    		if dt.month == month_server -1
                    			and dt.month ~= dt1.month then
                    			open_high_1 = H(indx)
                    			open_low_1 = L(indx)
                    		end
                    		if open_high_1 ~= nil and open_low_1 ~= nil
                    			and dt.month ~= month_server then
                    				if H(indx) > open_high_1 then
                    					open_high_1 = H(indx)
                    				end
                    				if L(indx)  open_high then
                    					open_high = H(indx)
                    				end
                    				if L(indx) < open_low then
                    					open_low = L(indx)
                    				end
                    		end
                    function GetServerDateTime()
                       local dt = {}
                       while RUN and dt.day == nil do
                          dt.day,dt.month,dt.year,dt.hour,dt.min,dt.sec = string.match(getInfoParam('TRADEDATE')..' '..getInfoParam('SERVERTIME'),"(%d*).(%d*).(%d*) (%d*):(%d*):(%d*)")
                          if dt.day == nil or isConnected() == 0 then WaitUpdateDataAfterReconnect() end
                       end
                       if not RUN then return os.date('*t', os.time()) end
                       for key,value in pairs(dt) do dt[key] = tonumber(value) end
                       return dt
                    end
                    1. unixtime - это число секунд, прошедшее с 1 января 1970 года. можно преобразовать таблицу, которую возвращают функции квика в этот формат.

                      1
                      2
                      
                      lua_dt = T(indx)
                      unix_dt = os.time (lua_dt)

                      зачем? затем, что с временем в секундах очень удобно работать. просто сравнивая целые числа можно легко понять, одна дата меньше другой или нет. если вычесть эти числа - то понятно насколько (в секундах).

                      так же довольно удобно вычислять моменты в прошлом и будущем. для этого нужно освоить две математические операции: деление нацело и получение остатка от деления.

                      если из текущего времени в unixtime отнять остаток от деления его на число x = 24*60*60, то мы получим 0 часов 0 минут текущего дня. прибавив 18 * 60 *60 + 45*60, мы получим значение 18:45 текущего дня - начало клиринга, с которым можно сравнивать текущее время, например:

                      1
                      
                      clr_time = unix_dt - unix_dt % (24*60*60) + 18 * 60 *60 + 45*60

                      если нужно просто сравнивать даты, то можно легко получить кол-во дней: day = unix_dt % (24*60*60), тогда "вчера" это day - 1, за "завтра" это days+1.

                      обратная функция для перевода unixtime в формат lua: os.date(). применять примерно так: os.date("*t", unix_time)

                    2. опечатался. day = math.floor(unix_dt / (24*60*60)). для вычисления остатка вместо оператора % так же можно использовать math.fmod(unix_dt, (24*60*60))

                  3. Добрый день! А если сразу использовать cur_dt = os.date("*t")? Тут получаем таблицу.
                    После, например, для вчерашнего дня yd = cur_dt.day - 1.
                    Чем лучше переводить в секунды, а потом группировать их обратно в минуты, часы, дни и т.д?
                    И вот ещё вопрос: os.time возвращает время компьютера. А если часовой пояс другой? Тогда и значения часов будут некорректными.?

                    1. тем, что для 1 числа месяца cur_dt.day - 1 будет равен нулю, что является ошибкой. нам нужно проверить, сколько дней в предыдущем месяце: 28, 29, 30 или 31 и и в этом случае присвоить соответствующее значение, уменьшить на единицу dt.month, уменьшить на единицу dt.year, если это было 1 января и присвоить month 12. учесть високосный год и все такое прочее.

                      если часовой пояс другой, то обычно принято все приводить к UTC.

                    2. ps: вся вот эта ваша хренота вроде "if dt.month == month_server -1" не будет нормально работать в январе.

                  4. Сколько тонкостей! Я-то планировал время, как свечи перебирать (годы, месяцы, часы, минуты). Наверно, суть в том, что в UNIX учитывается только реально прошедшее время, а не гипотетическое?!

    1. Здравствуйте,
      смотря какой расчет вы делаете за одну итерацию. 1 мс, обычно достаточно.
      Включите диспетчер задач и посмотрите нагрузку ЦП при включенном роботе и выключенном, при свернутом окне терминала.
      Если нагрузка бота на ЦП вас не устраивает, то оптимизируйте код или добавьте дополнительный sleep.

  4. Дмитрий, добрый день
    Столкнулся вот с какой проблемкой:
    запускаю скрипт, содержащий функцию остановки, после того как скрипт запущен, функция работает отлично, нажимаю стоп-все останавливается , далее снова включается и после нажатия стоп снова останавливает выполнение скрипта. Но стоит скрипту поработать какое то время, пройти несколько циклов и после нажатия на стоп-все зависает, скрипт выключается только после закрытия самого Quik через диспетчер задач.
    Подскажите пожалуйста в чем может быть проблема. Спасибо

    1. Здравствуйте, Андрей! С Новым Годом! 🙂 Это где-то в скрипте у Вас логическая ошибка, скорее всего где-то цикл while, который не смотрит на значение флага типа IsRun, который должен сбрасываться в OnStop.

  5. Можно ли запустить один скрипт, а из него запустить другие необходимые скрипты?
    При аварийных перезапусках Quik как автоматически запускать скрипты или один скрипт, который запустит остальные? Какие существуют методики контроля работы Quik и аварийных перезапусков его?

  6. Как перевести данные из потока Квика в function main()? Мне казалось, что любые глобальные переменные, если они меняются в функции обратного вызова должны быть видны в function main() уже измененными. Однако, в простейшем примере:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    
    function OnInit()
    IsRun = true
    time = os.clock()
    izm = false
    tbid = 0
    tbidvol = 0
    end
    -- проверка изменений в таблице
    function OnParam(class, sec)
       if (class =="SPBFUT" and  sec == "SiH7") then
          tbid = tonumber(getParamEx(class, sec, "bid").param_value)
          tbidvol = tonumber(getParamEx(class, sec, "BIDDEPTH").param_value)
    		 izm = true
       end
    end
     
    function main()
    	if izm then message("true",1) else message("false",1)
           end
       while IsRun do
          sleep(100) 
       end
    end
     
    function OnStop()
       IsRun = false
    end

    переменная izm при изменении в таблице параметров должна поменяться с izm = false на izm = true, однако она все время остается false.
    Я, по-видимому, в чем-то ошибаюсь?
    Но ведь переменная IsRun меняется в function OnStop().

      1. Большое спасибо!
        Ошибка-то элементарная, но в этом направлении даже не смотрел, думал не знаю каких-то основ в передаче данных в function main(), а найти их сам в интернете не смог. Вообще, почему-то, народ пишет роботов в основном, не пользуясь function main() и function OnInit(), хотя, насколько я понимаю, это достаточно важно для скорости исполнения.

        1. Всегда пожалуйста! Я вообще плохо представляю как можно написать робота без main 🙂 В конце этой статьи добавил маленькую сказочку по этому поводу 🙂 Прочтите, пожалуйста, надеюсь, Вам станут понятнее многие вещи.

  7. для чего именно нужна функция function OnInit() ?
    если я тоже самое пропишу просто перед main, не в какой либо функции, типо t=0, dt1=os.date("*t") и т.д. чем это отличается от прописанного в OnInit()? можно пример?

    1. В функцию OnInit передается полный путь к скрипту (когда-то это бывает нужно), еще Вы не сможете, например, использовать функцию CreateDataSource в самом теле скрипта, еще какие-то функции в самом теле скрипта не будут работать, но я не знаю весь список. Тут, вообще, нужно проще рассуждать, если Вам хватает того функционала, который у Вас есть, то и не усложняйте себе жизнь 🙂 Лично я практически никогда не использую данную функцию.

      1. Здравствуйте, Дмитрий. Не правда ваша, CreateDataSource вызывается нормально и без OnInit, как в прочем и остальные функции, перечисленные в руководстве.

        1. Здравствуйте! Запускаю сейчас вот такой скрипт на демке:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          
          ds, Error = CreateDataSource("QJSIM", "SBER", INTERVAL_M1);
           
          Run = true
           
          function main()
             while (Error == "" or Error == nil) and ds:Size() == 0 do sleep(1) end
             if Error ~= "" and Error ~= nil then message("Ошибка подключения к графику: "..Error) end
             while Run do
                sleep(10)
             end   
          end
           
          function OnStop()
             Run = false
          end

          и вижу сообщение "Ошибка подключения к графику: invalid context"

          1. Конечно будет ошибка, Вы пытаетесь нагрузить основной поток, вот так будет работать:

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            
            Run = true
             
            function main()
              ds, Error = CreateDataSource("QJSIM", "SBER", INTERVAL_M1);
             while (Error == "" or Error == nil) and ds:Size() == 0 do sleep(1) end
               if Error ~= "" and Error ~= nil then message("Ошибка подключения к графику: "..Error) end
               while Run do
                  sleep(10)
               end   
            end
             
            function OnStop()
               Run = false
            end
              1. Да внимательно, и речь шла о функции OnInit, а тело скрипта, по Вашему мнению - это основной поток. А какой частью "тела" является main? - нога?)))

                    1. Только что столкнулся с ситуацией, где OnInit() помогла.
                      Мне нужно перед началом работы скрипта вызвать функцию GetCurrentOffersAndBids(), использующей функцию getQuoteLevel2.

                      Если я сделаю это просто перед main(), то получу ошибку "attempt to call global 'GetCurrentOffersAndBids' (a nil value)". Помещение вызова GetCurrentOffersAndBids() внутрь OnInit() решило проблему.

  8. Если я правильно понимаю, то sleep работает корректно только в main'е. Чтобы иметь возможность приостановить работу скрипта из любой точки, я написал свой собственный sleep. Вдруг пригодится кому-то.

    1
    2
    3
    4
    5
    
    -- принимает время в секундах
    function Sleep(S)
      local ntime = os.time() + S;
      repeat until os.time() > ntime;
    end;
    1. Здравствуйте!
      Функция sleep везде работает корректно, она ставит на паузу тот поток, в котором она выполняется. Функция main - один поток (отдельный от основного), все функции, которые будут вызваны из функции main так же будут работать вне основного потока.
      Все остальное - другой поток (основной поток терминала).
      Отличием функции sleep от того, что предлагаете Вы, является то, что при вызове sleep процессор перестает обрабатывать поток, в котором вызвана эта функция, а в Вашем варианте Вы наоборот этот поток нагружаете. Чтобы понять о чем я говорю, запустите данный скрипт:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      
      run = true
       
      function main()
         while run do
            --sleep(1000)
            Sleep(1)
         end
      end
       
      function OnStop()
         message('До sleep')
         sleep(1000)
         message('После sleep')
         run = false
      end
       
      function Sleep(S)
        local ntime = os.time() + S;
        repeat until os.time() > ntime;
      end

      А теперь откройте "Диспетчер задач" Windows и посмотрите на сколько процентов QUIK загружает процессор, теперь остановите скрипт и посмотрите какое время будет у выведенных сообщений, а теперь раскомментируйте строку 5 и закомментируйте строку 6, запустите скрипт и посмотрите на сколько теперь процентов QUIK загружает процессор.
      Вообще, не стоит делать паузу в основном потоке, т.е. где-то кроме main, чтобы понять почему не стоит это делать, установите в строке 12 в функцию sleep значение 5000, запустите скрипт, потом остановите его и попробуйте в эти 5 секунд, с момента нажатия на кнопку "Остановить" подвигать график, например, Вы увидите, что терминал не реагирует на Ваши действия, потому что Вы приостановили основной поток выполнения терминала.

      1. Насчёт нагрузки на ЦП согласен. Действительно, возрастает многократно.
        Не понял насчёт потоков - можно ещё раз? Сам терминал выполняется в одном потоке, main - в другом. А что насчёт функций, вызванных из main? Вообще, все прочие функции в конце-концов вызываются через main. И они, насколько я понял, выполняются в том же потоке, что и сам терминал.
        Как-то было дело - писал скрипт, и почему-то ваш sleep не отрабатывал в вызванной функции. Поэтому написал свой. Сейчас погонял оба варианта - разницы не увидел, кроме нагрузки на процессор. Видимо, тогда я в чём-то ошибся.
        В общем, согласен, мой вариант лучше не использовать.

        1. Сам терминал и все функции обратного вызова скриптов работают в одном потоке, для того, чтобы в скрипте можно было делать какие-то ресурсоемкие вычисления и терминал при этом не "висел", разработчики сделали так, что для функции main создается отдельный поток, который никак не тормозит основной поток терминала. Чтобы объяснить на счет функций вызываемых из main, приведу тот же пример:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          
          run = true
           
          function main()
             while run do
                Sleep(5)
             end
          end
           
          function OnStop()
             Sleep(5)
             run = false
          end
           
          function Sleep(S)
            local ntime = os.time() + S;
            repeat until os.time() > ntime;
          end

          Если Вы его запустите, то увидите в диспетчере задач, что использование процессора выросло, т.к. в main используется Ваш Sleep, но терминал при этом реагирует на действия пользователя, потому что Sleep выполняется в отдельном от терминала потоке (вызывается в main). Но когда Вы остановите скрипт, то Sleep будет вызвана не в отдельном потоке main, а а в основном, как я писал выше, все функции обратного вызова выполняются в основном потоке и OnStop не исключение. Важно понимать следующее, функция - это инструкция записанная в память, сама по себе она ничего не делает. Когда в терминале запускается скрипт, то терминал находит в скрипте функцию main, он знает, что для main нужно создавать отдельный поток, он его создает и начинает в нем выполнять все инструкции написанные в main, т.е. для каждого скрипта есть свой отдельный поток main. Так же, при инициализации скрипта терминал смотрит какие On-функции есть в скрипте и запоминает их, функции обратного вызова это ничто иное, как подписки на события происходящие в терминала.
          И вот работает терминал в своем потоке, работают параллельно потоки main запущенных скриптов, вдруг в терминале происходит какое-то событие, как в нашем примере, пользователь нажал "Остановить", терминал знает, что скрипты могут подписываться на это событие при помощи функции OnStop, терминал знает уже, что останавливаемый скрипт подписался на это событие и выполняет инструкции написанные в функции OnStop, и выполняет он их в своем потоке, а поток main так и продолжает работать параллельно, ничего не подозревая. Но во время выполнения функции OnStop было изменено значение глобальной переменной run, цикл while остановился, функция main выполнила все инструкции в ней прописанные, потоку main больше нечего стало делать, он завершился, а значит завершилось выполнение скрипта, потому что скрипт это и есть поток main.
          Стало понятнее?