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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 | -- Скрипт позволяет открывать и закрывать позиции следующего типа: -- покупается 2, выбранных при помощи кнопок, колл-опциона на фьючерс на индекс РТС, -- продается 1, выбранный при помощи кнопок, фьючерс на индекс РТС. -- Когда есть открытая данным скриптом позиция, в таблице отображается информация о ней (профит, баланс, ср.цена, дата открытия). -- Профит вычисляется по формуле: "Текущая цена, по которой можно закрыть позицию" - "Цена открытия позиции" - "Комиссия 8 р. на каждый лот" -- Так же, выполняется запись в лог-файл ("Log.txt") выполняемых скриптом операций -- и в файле состояния ("State.txt") хранится информация о текущих выбранных инструментах и о текущей, открытой скриптом позиции -- ВАЖНО!!! При выборе опциона, кнопками «/\»,»\/» меняется страйк опциона с шагом 500, убедитесь в том, что такой опцион существует!!! -- (c) QuikLuaCSharp.ru -- Флаг поддержания работы скрипта IsRun = true; COMMISSION = 4; -- Комиссия (примерно) (биржа + брокер) за 1-у торговую операцию по 1-му лоту опциона, или фьючерса TRADE_ACC = "SPBFUT00136"; -- Торговый счет CLIENT_CODE = "SPBFUT00136"; -- Код клиента CLASS_CODE_OPT = "SPBOPT"; -- Класс ОПЦИОНОВ CLASS_CODE_FUT = "SPBFUT"; -- Класс ФЬЮЧЕРСОВ SEC_CODE_FUT_FOR_OPEN = "RIM5"; -- Код ФЬЮЧЕРСА для открытия SEC_CODE_OPT_FOR_OPEN = "RI110000BF5"; -- Код ОПЦИОНА для открытия SEC_CODE_OPT_IN_POS = ""; -- Код ОПЦИОНА в позиции OPEN_BALANCE_OPT = 0; -- Баланс позиции по ОПЦИОНАМ OPEN_PRICE_OPT = 0; -- Средняя цена открытия позиции по ОПЦИОНАМ OPEN_DATE_OPT = ""; -- Дата открытия позиции по ОПЦИОНАМ SEC_CODE_FUT_IN_POS = ""; -- Код ФЬЮЧЕРСА в позиции OPEN_BALANCE_FUT = 0; -- Баланс позиции по ФЬЮЧЕРСАМ OPEN_PRICE_FUT = 0; -- Средняя цена открытия позиции по ФЬЮЧЕРСАМ OPEN_DATE_FUT = ""; -- Дата открытия позиции по ФЬЮЧЕРСАМ -- Флаг для поддержания работы функции main IsRun = true; trans_id_OPT = nil; -- ID заявки на опционы OrderNum_OPT = nil; -- Номер заявки на опционы в торговой системе trans_id_FUT = nil; -- ID заявки на фьючерс OrderNum_FUT = nil; -- Номер заявки на фьючерс в торговой системе -- Константы для строк таблицы BUTTON_UP_FUTCODE_LINE = 1; -- Строка кнопки увеличения даты фьючерса SEC_CODE_FUT_LINE = 2; -- Строка кода фьючерса BUTTON_DOWN_FUTCODE_LINE = 3; -- Строка кнопки уменьшения даты фьючерса -- 4; -- Пустая строка BUTTON_UP_STRIKE_LINE = 5; -- Строка кнопки увеличения значений страйка опциона SEC_CODE_OPT_LINE = 6; -- Строка кода опциона BUTTON_DOWN_STRIKE_LINE = 7; -- Строка кнопки уменьшения значений страйка опциона -- 8; -- Пустая строка BUTTON_OPEN_LINE = 9; -- Строка с кнопкой "Открыть" -- 10; -- Пустая строка -- 11; -- Пустая строка INFO_TITLE_LINE = 12; -- Строка заголовков информации по позиции OPT_PROFIT_LINE = 13; -- Строка профита по опционам FUT_PROFIT_LINE = 14; -- Строка профита по фьючерсу ALL_PROFIT_LINE = 15; -- Строка суммарного профита -- 16; -- Пустая строка BUTTON_CLOSE_LINE = 17; -- Строка с кнопкой "Закрыть" -- Флаги возможности открытия/закрытия позиции MayOpen = true; MayClose = false; STATE = nil; -- Флаг текущего процесса ("OPEN"/"CLOSE") trans_id_OPT = nil; -- ID транзакции на покупку ОПЦИОНОВ trans_id_FUT = nil; -- ID транзакции на продажу ФЬЮЧЕРСА NotBuy_OPT = true; -- Флаг, что ОПЦИОНЫ еще не куплены NotSell_OPT = true; -- Флаг, что ОПЦИОНЫ еще не проданы NotBuy_FUT = true; -- Флаг, что ФЬЮЧЕРСЫ еще не куплены NotSell_FUT = true; -- Флаг, что ФЬЮЧЕРСЫ еще не проданы OpenPosProcessed = false; -- Флаг, что открытие позиций обработано ClosePosProcessed = false; -- Флаг, что закрытие позиций обработано OrderNum_OPT = nil; -- Номер заявки на ОПЦИОНЫ в торговой системе OrderNum_FUT = nil; -- Номер заявки на ФЬЮЧЕРС в торговой системе function OnInit() -- Пытается открыть лог-файл в режиме "чтения/записи" Log = io.open(getScriptPath().."//Log.txt","r+"); -- Если файл не существует if Log == nil then -- Создает файл в режиме "записи" Log = io.open(getScriptPath().."//Log.txt","w"); -- Закрывает файл Log:close(); -- Открывает уже существующий файл в режиме "чтения/записи" Log = io.open(getScriptPath().."//Log.txt","r+"); end; -- Встает в конец файла Log:seek("end",0); -- Добавляет пустую строку-разрыв Log:write("\n");Log:flush(); -- Пытается открыть файл состояния в режиме "чтения/записи" local f = io.open(getScriptPath().."//State.txt","r+"); -- Если файл существует if f ~= nil then -- Перебирает строки файла, считывает содержимое в соответствующие переменные local Count = 0; -- Счетчик строк for line in f:lines() do Count = Count + 1; if Count == 1 then SEC_CODE_FUT_FOR_OPEN = line; -- Код ФЬЮЧЕРСА для открытия elseif Count == 2 then SEC_CODE_OPT_FOR_OPEN = line; -- Код ОПЦИОНА для открытия elseif Count == 3 then SEC_CODE_OPT_IN_POS = line; -- Код ОПЦИОНА в позиции -- ЕСЛИ есть открытая позиция, ТО разрешается закрывать позицию, ИНАЧЕ, разрешается открывать позицию if SEC_CODE_OPT_IN_POS ~= "" then MayOpen = false; MayClose = true; NotBuy_OPT = true; NotSell_FUT = true; else MayOpen = true; end; elseif Count == 4 then OPEN_BALANCE_OPT = tonumber(line); -- Количество лотов в позиции по ОПЦИОНАМ elseif Count == 5 then OPEN_PRICE_OPT = tonumber(line); -- Средняя цена открытия позиции по ОПЦИОНАМ elseif Count == 6 then OPEN_DATE_OPT = line; -- Дата открытия позиции по ОПЦИОНАМ elseif Count == 7 then SEC_CODE_FUT_IN_POS = line; -- Код ФЬЮЧЕРСА в позиции elseif Count == 8 then OPEN_BALANCE_FUT = tonumber(line); -- Количество лотов в позиции по ФЬЮЧЕРСАМ elseif Count == 9 then OPEN_PRICE_FUT = tonumber(line); -- Средняя цена открытия позиции по ФЬЮЧЕРСАМ elseif Count == 10 then OPEN_DATE_FUT = line; -- Дата открытия позиции по ФЬЮЧЕРСАМ end; end; -- Закрывает файл f:close(); end; -- Создает таблицу CreateTable(); -- Назначает таблице t_id функцию обработки событий event_callback_message SetTableNotificationCallback(t_id, event_callback_message); -- Инициализирует генератор math.randomseed(os.date("*t",os.time()).sec); -- Инициализирует генератор псевдослучайных чисел параметром, каждый параметр порождает соответствующую (но одну и ту же) последовательность псевдослучайных чисел. end; function main() -- Цикл будет выполнятся, пока IsRun == true while IsRun do -- ЕСЛИ текущий процесс ОТКРЫТИЕ позиции и ОПЦИОНЫ куплены и ФЬЮЧЕРС продан и открытие позиций еще не обработано, ТО if STATE == "OPEN" and not NotBuy_OPT and not NotSell_FUT and not OpenPosProcessed then -- Функция обработки открытия позиций OpenPosProcess(); end; -- ЕСЛИ текущий процесс ЗАКРЫТИЕ позиции и ОПЦИОНЫ проданы и ФЬЮЧЕРС куплен и закрытие позиций еще не обработано, ТО if STATE == "CLOSE" and not NotSell_OPT and not NotBuy_FUT and not ClosePosProcessed then -- Функция обработки закрытия позиций ClosePosProcess(); end; sleep(100); end; end; -- Функция для записи в лог действий скрипта function ToLog(str) local datetime = os.date("*t",os.time()); -- Текущие дата/время local sec_mcs_str = tostring(os.clock()); -- Секунды с микросекундами local mcs_str = string.sub(sec_mcs_str, sec_mcs_str:find("%.") + 1); -- Микросекунды -- Записывает в лог-файл переданную строку, добавляя в ее начало время с точностью до микросекунд Log:write(tostring(datetime.day).."-" ..tostring(datetime.month).."-" ..tostring(datetime.year).." " ..tostring(datetime.hour)..":" ..tostring(datetime.min)..":" ..tostring(datetime.sec).."." ..mcs_str.." " ..str.."\n"); -- Записывает в лог-файл Log:flush(); -- Сохраняет изменения в лог-файле end; -- Функция ОТКРЫВАЕТ позицию function OpenPosition() ToLog("OpenPosition"); -- Записывает в лог-файл -- Устанавливает флаг текущего процесса в ОТКРЫТИЕ позиции STATE = "OPEN"; -- Сбрасывает флаг обработки открытия позиций OpenPosProcessed = false; trans_id_OPT = math.random(1,9999); ToLog("trans_id_OPT = "..tostring(trans_id_OPT)); -- Записывает в лог-файл -- Заполняет структуру для отправки транзакции на покупку 2-х ОПЦИОНОВ local Transaction={ ["TRANS_ID"] = tostring(trans_id_OPT), ["ACTION"] = "NEW_ORDER", ["CLASSCODE"] = CLASS_CODE_OPT, ["SECCODE"] = SEC_CODE_OPT_FOR_OPEN, ["OPERATION"] = "B", -- покупка (BUY) ["TYPE"] = "M", -- по рынку (MARKET) ["QUANTITY"] = "2", -- количество ["ACCOUNT"] = TRADE_ACC, ["PRICE"] = tostring(getParamEx(CLASS_CODE_OPT, SEC_CODE_OPT_FOR_OPEN, "offer").param_value + 10*getParamEx(CLASS_CODE_OPT, SEC_CODE_OPT_FOR_OPEN, "SEC_PRICE_STEP").param_value), -- по цене, завышенной на 10 мин. шагов цены ["COMMENT"] = "Покупка опционов скриптом" } -- Флаг, что ОПЦИОНЫ еще не куплены NotBuy_OPT = true; local Result = sendTransaction(Transaction); ToLog("Заявка на покупку ОПЦИОНОВ отправлена"); -- Записывает в лог-файл ToLog("Result = "..Result); -- Записывает в лог-файл -- ЕСЛИ функция вернула строку диагностики ошибки, ТО значит транзакция не прошла if Result ~= "" then -- Выводит сообщение с ошибкой message("Покупка ОПЦИОНОВ не удалась!\nОШИБКА: "..Result); -- Завершает выполнение функции return(0); end; -- Генерирует случайный ID транзакции на продажу ФЬЮЧЕРСОВ trans_id_FUT = math.random(1,9999); ToLog("trans_id_FUT = "..tostring(trans_id_FUT)); -- Записывает в лог-файл -- Заполняет структуру для отправки транзакции на продажу ФЬЮЧЕРСОВ local Transaction={ ["TRANS_ID"] = tostring(trans_id_FUT), ["ACTION"] = "NEW_ORDER", ["CLASSCODE"] = CLASS_CODE_FUT, ["SECCODE"] = SEC_CODE_FUT_FOR_OPEN, ["OPERATION"] = "S", -- продажа (SELL) ["TYPE"] = "M", -- по рынку (MARKET) ["QUANTITY"] = "1", -- количество ["ACCOUNT"] = TRADE_ACC, ["PRICE"] = tostring(getParamEx(CLASS_CODE_FUT, SEC_CODE_FUT_FOR_OPEN, "bid").param_value - 10*getParamEx(CLASS_CODE_FUT, SEC_CODE_FUT_FOR_OPEN, "SEC_PRICE_STEP").param_value), -- по цене, заниженной на 10 мин. шагов цены ["COMMENT"] = "Продажа фьючерсов скриптом" } -- Флаг, что ФЬЮЧЕРСЫ еще не проданы NotSell_FUT = true; local Result = sendTransaction(Transaction); ToLog("Заявка на продажу ФЬЮЧЕРСОВ отправлена"); -- Записывает в лог-файл ToLog("Result = "..Result); -- Записывает в лог-файл -- ЕСЛИ функция вернула строку диагностики ошибки, ТО значит транзакция не прошла if Result ~= "" then -- Выводит сообщение с ошибкой message("Продажа ФЬЮЧЕРСОВ не удалась!\nОШИБКА: "..Result); -- Завершает выполнение функции return(0); end; end; -- Функция ЗАКРЫВАЕТ позицию function ClosePosition() ToLog("ClosePosition"); -- Записывает в лог-файл -- Устанавливает флаг текущего процесса в ЗАКРЫТИЕ позиции STATE = "CLOSE"; -- Сбрасывает флаг обработки закрытия позиций ClosePosProcessed = false; trans_id_OPT = math.random(1,9999); ToLog("trans_id_OPT = "..tostring(trans_id_OPT)); -- Записывает в лог-файл -- Заполняет структуру для отправки транзакции на продажу 2-х ОПЦИОНОВ local Transaction={ ["TRANS_ID"] = tostring(trans_id_OPT), ["ACTION"] = "NEW_ORDER", ["CLASSCODE"] = CLASS_CODE_OPT, ["SECCODE"] = SEC_CODE_OPT_IN_POS, ["OPERATION"] = "S", -- продажа (SELL) ["TYPE"] = "M", -- по рынку (MARKET) ["QUANTITY"] = "2", -- количество ["ACCOUNT"] = TRADE_ACC, ["PRICE"] = tostring(getParamEx(CLASS_CODE_OPT, SEC_CODE_OPT_FOR_OPEN, "bid").param_value - 10*getParamEx(CLASS_CODE_OPT, SEC_CODE_OPT_FOR_OPEN, "SEC_PRICE_STEP").param_value), -- по цене, заниженной на 10 мин. шагов цены ["COMMENT"] = "Продажа опционов скриптом" } -- Флаг, что ОПЦИОНЫ еще не проданы NotSell_OPT = true; local Result = sendTransaction(Transaction); ToLog("Заявка на продажу ОПЦИОНОВ отправлена"); -- Записывает в лог-файл ToLog("Result = "..Result); -- Записывает в лог-файл -- ЕСЛИ функция вернула строку диагностики ошибки, ТО значит транзакция не прошла if Result ~= "" then -- Выводит сообщение с ошибкой message("Продажа ОПЦИОНОВ не удалась!\nОШИБКА: "..Result); -- Завершает выполнение функции return(0); end; -- Генерирует случайный ID транзакции на покупку ФЬЮЧЕРСОВ trans_id_FUT = math.random(1,9999); ToLog("trans_id_FUT = "..tostring(trans_id_FUT)); -- Записывает в лог-файл -- Заполняет структуру для отправки транзакции на покупку ФЬЮЧЕРСОВ local Transaction={ ["TRANS_ID"] = tostring(trans_id_FUT), ["ACTION"] = "NEW_ORDER", ["CLASSCODE"] = CLASS_CODE_FUT, ["SECCODE"] = SEC_CODE_FUT_IN_POS, ["OPERATION"] = "B", -- покупка (BUY) ["TYPE"] = "M", -- по рынку (MARKET) ["QUANTITY"] = "1", -- количество ["ACCOUNT"] = TRADE_ACC, ["PRICE"] = tostring(getParamEx(CLASS_CODE_FUT, SEC_CODE_FUT_FOR_OPEN, "offer").param_value + 10*getParamEx(CLASS_CODE_FUT, SEC_CODE_FUT_FOR_OPEN, "SEC_PRICE_STEP").param_value), -- по цене, завышенной на 10 мин. шагов цены ["COMMENT"] = "Покупка фьючерсов скриптом" } -- Флаг, что ФЬЮЧЕРСЫ еще не куплены NotBuy_FUT = true; local Result = sendTransaction(Transaction); ToLog("Заявка на покупку ФЬЮЧЕРСОВ отправлена"); -- Записывает в лог-файл ToLog("Result = "..Result); -- Записывает в лог-файл -- ЕСЛИ функция вернула строку диагностики ошибки, ТО значит транзакция не прошла if Result ~= "" then -- Выводит сообщение с ошибкой message("Покупка ФЬЮЧЕРСОВ не удалась!\nОШИБКА: "..Result); -- Завершает выполнение функции return(0); end; end; -- Функция обработки открытия позиций function OpenPosProcess() OPEN_BALANCE_OPT = 2; -- Запоминает баланс позиции ОПЦИОНОВ OPEN_BALANCE_FUT = -1; -- Запоминает баланс позиции ФЬЮЧЕРСОВ -- Получает среднюю цену позиции по ОПЦИОНАМ local LotSumOPT = 0; -- Общее количество лотов ОПЦИОНОВ в сделке(ах) по данной заявке local LotSumFUT = 0; -- Общее количество лотов ФЬЮЧЕРСОВ в сделке(ах) по данной заявке -- Бесконечный цикл, на случай, если информация о сделке придет позже while true do -- Обнуляет перед каждым новым проходом по таблице сделок OPEN_PRICE_OPT = 0; OPEN_PRICE_FUT = 0; LotSumOPT = 0; LotSumFUT = 0; -- Перебирает все строки в таблице сделок for i = 0,getNumberOf("TRADES") - 1 do -- ЕСЛИ сделка по ОПЦИОННОЙ заявке, ТО if getItem("TRADES",i).order_num == OrderNum_OPT then -- Считает сумму цен в сделках OPEN_PRICE_OPT = OPEN_PRICE_OPT + getItem("TRADES",i).price * getItem("TRADES",i).qty; -- Считает количество лотов в сделках LotSumOPT = LotSumOPT + getItem("TRADES",i).qty; -- Запоминает дату открытия позиции по ОПЦИОНАМ OPEN_DATE_OPT = tostring(getItem("TRADES",i).datetime.day).."-"..tostring(getItem("TRADES",i).datetime.month).."-"..tostring(getItem("TRADES",i).datetime.year); elseif getItem("TRADES",i).order_num == OrderNum_FUT then -- ЕСЛИ сделка по ФЬЮЧЕРСНОЙ заявке, ТО -- Считает сумму цен в сделках OPEN_PRICE_FUT = OPEN_PRICE_FUT + getItem("TRADES",i).price * getItem("TRADES",i).qty; -- Считает количество лотов в сделках LotSumFUT = LotSumFUT + getItem("TRADES",i).qty; -- Запоминает дату открытия позиции по ФЬЮЧЕРСАМ OPEN_DATE_FUT = tostring(getItem("TRADES",i).datetime.day).."-"..tostring(getItem("TRADES",i).datetime.month).."-"..tostring(getItem("TRADES",i).datetime.year); end; end; -- ЕСЛИ все лоты найдены в сделках по ОПЦИОННОЙ и ФЬЮЧЕРСНОЙ заявкам, ТО if LotSumOPT == math.abs(OPEN_BALANCE_OPT) and LotSumFUT == math.abs(OPEN_BALANCE_FUT) then ToLog("LotSumOPT = "..tostring(LotSumOPT).." LotSumFUT = "..tostring(LotSumFUT)); -- Запоминает код ОПЦИОНА в позиции SEC_CODE_OPT_IN_POS = SEC_CODE_OPT_FOR_OPEN; -- Запоминает код ФЬЮЧЕРСА в позиции SEC_CODE_FUT_IN_POS = SEC_CODE_FUT_FOR_OPEN; -- Вычисляет среднюю цену по ОПЦИОНАМ OPEN_PRICE_OPT = OPEN_PRICE_OPT/math.abs(OPEN_BALANCE_OPT); -- Вычисляет среднюю цену по ФЬЮЧЕРСАМ OPEN_PRICE_FUT = OPEN_PRICE_FUT/math.abs(OPEN_BALANCE_FUT); -- Меняет флаги возможности открытия/закрытия позиции MayOpen = false; MayClose = true; -- Событие открытия позиций обработано OpenPosProcessed = true; -- Текущий процесс отсутствует STATE = nil; -- Выводит в таблицу новые данные SetCell(t_id, OPT_PROFIT_LINE, 2, tostring(OPEN_BALANCE_OPT)); SetCell(t_id, OPT_PROFIT_LINE, 3, tostring(OPEN_PRICE_OPT)); SetCell(t_id, OPT_PROFIT_LINE, 4, tostring(OPEN_DATE_OPT)); SetCell(t_id, FUT_PROFIT_LINE, 2, tostring(OPEN_BALANCE_FUT)); SetCell(t_id, FUT_PROFIT_LINE, 3, tostring(OPEN_PRICE_FUT)); SetCell(t_id, FUT_PROFIT_LINE, 4, tostring(OPEN_DATE_FUT)); -- Меняет подсветку кнопок Gray(BUTTON_OPEN_LINE, 1); Green(BUTTON_CLOSE_LINE, 1); break; -- Завершает бесконечный цикл end; sleep(100); end; end; -- Функция обработки закрытия позиций function ClosePosProcess() -- Обнуляет все флаги SEC_CODE_OPT_IN_POS = ""; -- Код ОПЦИОНА в позиции OPEN_BALANCE_OPT = 0; -- Баланс позиции по ОПЦИОНАМ OPEN_PRICE_OPT = 0; -- Средняя цена открытия позиции по ОПЦИОНАМ OPEN_DATE_OPT = ""; -- Дата открытия позиции по ОПЦИОНАМ SEC_CODE_FUT_IN_POS = ""; -- Код ФЬЮЧЕРСА в позиции OPEN_BALANCE_FUT = 0; -- Баланс позиции по ФЬЮЧЕРСАМ OPEN_PRICE_FUT = 0; -- Средняя цена открытия позиции по ФЬЮЧЕРСАМ OPEN_DATE_FUT = ""; -- Дата открытия позиции по ФЬЮЧЕРСАМ -- Меняет флаги возможности открытия/закрытия позиции MayOpen = true; MayClose = false; -- Событие открытия позиций обработано ClosePosProcessed = true; -- Текущий процесс отсутствует STATE = nil; -- Выводит в таблицу новые данные SetCell(t_id, OPT_PROFIT_LINE, 1, tostring(0)); SetCell(t_id, OPT_PROFIT_LINE, 2, tostring(OPEN_BALANCE_OPT)); SetCell(t_id, OPT_PROFIT_LINE, 3, tostring(OPEN_PRICE_OPT)); SetCell(t_id, OPT_PROFIT_LINE, 4, tostring(OPEN_DATE_OPT)); SetCell(t_id, FUT_PROFIT_LINE, 1, tostring(0)); SetCell(t_id, FUT_PROFIT_LINE, 2, tostring(OPEN_BALANCE_FUT)); SetCell(t_id, FUT_PROFIT_LINE, 3, tostring(OPEN_PRICE_FUT)); SetCell(t_id, FUT_PROFIT_LINE, 4, tostring(OPEN_DATE_FUT)); SetCell(t_id, ALL_PROFIT_LINE, 1, tostring(0)); -- Меняет подсветку кнопок Green(BUTTON_OPEN_LINE, 1); Gray(BUTTON_CLOSE_LINE, 1); end; -- Функция обработки событий в таблице function event_callback_message(t_id, msg, par1, par2) -- ЕСЛИ нажата кнопка "/\" для ФЬЮЧЕРСА, ТО if msg == QTABLE_LBUTTONUP and par1 == BUTTON_UP_FUTCODE_LINE and par2 == 1 then -- Получает символьную часть ФЬЮЧЕРСА (RIH->RIM->RIU->RIZ) local FutSecCode_STR = string.sub(SEC_CODE_FUT_FOR_OPEN, 1, 3); -- Получает числовую часть (0-9) local FutSecCode_NUM = tonumber(string.sub(SEC_CODE_FUT_FOR_OPEN, 4, 4)); -- Увеличивает значение if FutSecCode_STR == "RIH" then FutSecCode_STR = "RIM"; elseif FutSecCode_STR == "RIM" then FutSecCode_STR = "RIU"; elseif FutSecCode_STR == "RIU" then FutSecCode_STR = "RIZ"; elseif FutSecCode_STR == "RIZ" then FutSecCode_STR = "RIH"; FutSecCode_NUM = FutSecCode_NUM + 1; if FutSecCode_NUM == 10 then FutSecCode_NUM = 0; end; end; -- Запоминает новое значение ФЬЮЧЕРСА для открытия SEC_CODE_FUT_FOR_OPEN = FutSecCode_STR..tostring(FutSecCode_NUM); -- Выводит новое значение ФЬЮЧЕРСА в таблицу SetCell(t_id, SEC_CODE_FUT_LINE, 1, SEC_CODE_FUT_FOR_OPEN.." "); -- Подстраивает значение ОПЦИОНА под выбранный ФЬЮЧЕРС и запоминает его SEC_CODE_OPT_FOR_OPEN = string.sub(SEC_CODE_OPT_FOR_OPEN, 1, #SEC_CODE_OPT_FOR_OPEN - 1)..tostring(FutSecCode_NUM); -- Выводит новое значение ОПЦИОНА в таблицу SetCell(t_id, SEC_CODE_OPT_LINE, 1, SEC_CODE_OPT_FOR_OPEN); end; -- ЕСЛИ нажата кнопка "\/" для ФЬЮЧЕРСА, ТО if msg == QTABLE_LBUTTONUP and par1 == BUTTON_DOWN_FUTCODE_LINE and par2 == 1 then -- Получает текущее значение local FutSecCode = GetCell(t_id, SEC_CODE_FUT_LINE, 1); -- Получает символьную часть (RIH->RIM->RIU->RIZ) local FutSecCode_STR = string.sub(FutSecCode.image, 1, 3); -- Получает числовую часть (0-9) local FutSecCode_NUM = tonumber(string.sub(FutSecCode.image, 4, 4)); -- Уменьшает значение if FutSecCode_STR == "RIZ" then FutSecCode_STR = "RIU"; elseif FutSecCode_STR == "RIU" then FutSecCode_STR = "RIM"; elseif FutSecCode_STR == "RIM" then FutSecCode_STR = "RIH"; elseif FutSecCode_STR == "RIH" then FutSecCode_STR = "RIZ"; FutSecCode_NUM = FutSecCode_NUM - 1; if FutSecCode_NUM == -1 then FutSecCode_NUM = 9; end; end; -- Запоминает новое значение ФЬЮЧЕРСА для открытия SEC_CODE_FUT_FOR_OPEN = FutSecCode_STR..tostring(FutSecCode_NUM); -- Выводит новое значение ФЬЮЧЕРСА в таблицу SetCell(t_id, SEC_CODE_FUT_LINE, 1, SEC_CODE_FUT_FOR_OPEN.." "); -- Подстраивает значение ОПЦИОНА под выбранный ФЬЮЧЕРС и запоминает его SEC_CODE_OPT_FOR_OPEN = string.sub(SEC_CODE_OPT_FOR_OPEN, 1, #SEC_CODE_OPT_FOR_OPEN - 1)..tostring(FutSecCode_NUM); -- Выводит новое значение ОПЦИОНА в таблицу SetCell(t_id, SEC_CODE_OPT_LINE, 1, SEC_CODE_OPT_FOR_OPEN); end; -- ЕСЛИ нажата кнопка "/\" для ОПЦИОНА, ТО if msg == QTABLE_LBUTTONUP and par1 == BUTTON_UP_STRIKE_LINE and par2 == 1 then -- Получает цифры СТРАЙКА из кода ОПЦИОНА local OptSecCode_STRIKE = tonumber(string.sub(SEC_CODE_OPT_FOR_OPEN, 3, #SEC_CODE_OPT_FOR_OPEN - 3)); -- Увеличивает СТРАЙК (здесь задан шаг 500, смотрите, чтобы такой опцион существовал!!!) OptSecCode_STRIKE = OptSecCode_STRIKE + 500; -- Подстраивает значение ОПЦИОНА, запоминает SEC_CODE_OPT_FOR_OPEN = string.sub(SEC_CODE_OPT_FOR_OPEN, 1, 2)..tostring(OptSecCode_STRIKE)..string.sub(SEC_CODE_OPT_FOR_OPEN, #SEC_CODE_OPT_FOR_OPEN - 2, #SEC_CODE_OPT_FOR_OPEN); -- Выводит новое значение ОПЦИОНА в таблицу SetCell(t_id, SEC_CODE_OPT_LINE, 1, SEC_CODE_OPT_FOR_OPEN); end; -- ЕСЛИ нажата кнопка "\/" для ОПЦИОНА, ТО if msg == QTABLE_LBUTTONUP and par1 == BUTTON_DOWN_STRIKE_LINE and par2 == 1 then -- Получает цифры СТРАЙКА из кода ОПЦИОНА local OptSecCode_STRIKE = tonumber(string.sub(SEC_CODE_OPT_FOR_OPEN, 3, #SEC_CODE_OPT_FOR_OPEN - 3)); -- Уменьшает СТРАЙК (здесь задан шаг 500, смотрите, чтобы такой опцион существовал!!!) OptSecCode_STRIKE = OptSecCode_STRIKE - 500; -- Подстраивает значение ОПЦИОНА, запоминает SEC_CODE_OPT_FOR_OPEN = string.sub(SEC_CODE_OPT_FOR_OPEN, 1, 2)..tostring(OptSecCode_STRIKE)..string.sub(SEC_CODE_OPT_FOR_OPEN, #SEC_CODE_OPT_FOR_OPEN - 2, #SEC_CODE_OPT_FOR_OPEN); -- Выводит новое значение ОПЦИОНА в таблицу SetCell(t_id, SEC_CODE_OPT_LINE, 1, SEC_CODE_OPT_FOR_OPEN); end; -- ЕСЛИ нажата кнопка "Открыть", ТО if msg == QTABLE_LBUTTONUP and par1 == BUTTON_OPEN_LINE and par2 == 1 then -- ЕСЛИ нет возможности открыть позицию (уже открыта), ТО if not MayOpen then -- Выводит сообщение message("ЗАПРЕЩЕНО! Позиция уже открыта!!!", 3); else -- ИНАЧЕ -- Открывает позицию OpenPosition(); end; end; -- ЕСЛИ нажата кнопка "Закрыть" if msg == QTABLE_LBUTTONUP and par1 == BUTTON_CLOSE_LINE and par2 == 1 then -- ЕСЛИ нет возможности закрыть позицию (еще не открыта), ТО if not MayClose then -- Выводит сообщение message("НЕВОЗМОЖНО! Позиция еще не открыта!!!", 3); else -- ИНАЧЕ -- Закрывает позицию ClosePosition(); end; end; end; --- Функция создает таблицу function CreateTable() -- Получает доступный id для создания t_id = AllocTable(); -- Добавляет 5 колонок AddColumn(t_id, 0, "", true, QTABLE_STRING_TYPE, 24); AddColumn(t_id, 1, "", true, QTABLE_INT_TYPE, 15); AddColumn(t_id, 2, "", true, QTABLE_STRING_TYPE, 15); AddColumn(t_id, 3, "", true, QTABLE_STRING_TYPE, 15); AddColumn(t_id, 4, "", true, QTABLE_STRING_TYPE, 15); -- Создает таблицу t = CreateWindow(t_id); -- Устанавливает заголовок SetWindowCaption(t_id, "Открыть/закрыть +2 ОПЦИОНА -1 ФЬЮЧЕРС"); -- Задает положение и размеры окна таблицы SetWindowPos(t_id, 100, 100, 472, 325); -- Добавляет строки InsertRow(t_id, BUTTON_UP_FUTCODE_LINE); -- Строка кнопки увеличения даты фьючерса InsertRow(t_id, SEC_CODE_FUT_LINE); -- Строка кода фьючерса InsertRow(t_id, BUTTON_DOWN_FUTCODE_LINE);-- Строка кнопки уменьшения даты фьючерса InsertRow(t_id, 4); -- Пустая строка InsertRow(t_id, BUTTON_UP_STRIKE_LINE); -- Строка кнопки увеличения значений страйка опциона InsertRow(t_id, SEC_CODE_OPT_LINE); -- Строка кода опциона InsertRow(t_id, BUTTON_DOWN_STRIKE_LINE); -- Строка кнопки уменьшения значений страйка опциона InsertRow(t_id, 8); -- Пустая строка InsertRow(t_id, BUTTON_OPEN_LINE); -- Строка с кнопкой "Открыть" InsertRow(t_id, 10); -- Пустая строка InsertRow(t_id, 11); -- Пустая строка InsertRow(t_id, INFO_TITLE_LINE); -- Строка заголовков информации по позиции InsertRow(t_id, OPT_PROFIT_LINE); -- Строка профита по опционам InsertRow(t_id, FUT_PROFIT_LINE); -- Строка профита по фьючерсу InsertRow(t_id, ALL_PROFIT_LINE); -- Строка суммарного профита InsertRow(t_id, 16); -- Пустая строка InsertRow(t_id, BUTTON_CLOSE_LINE); -- Строка с кнопкой "Закрыть" -- Добавляет значения в ячейки, окрашивает кнопки SetCell(t_id, BUTTON_UP_FUTCODE_LINE, 1, "/\\ ");Green(BUTTON_UP_FUTCODE_LINE, 1); SetCell(t_id, SEC_CODE_FUT_LINE, 0, "Код ФЬЮЧЕРСА"); SetCell(t_id, SEC_CODE_FUT_LINE, 1, SEC_CODE_FUT_FOR_OPEN.." "); SetCell(t_id, BUTTON_DOWN_FUTCODE_LINE, 1, "\\/ ");Green(BUTTON_DOWN_FUTCODE_LINE, 1); SetCell(t_id, BUTTON_UP_STRIKE_LINE, 1, "/\\ ");Green(BUTTON_UP_STRIKE_LINE, 1); SetCell(t_id, SEC_CODE_OPT_LINE, 0, "Код ОПЦИОНА"); SetCell(t_id, SEC_CODE_OPT_LINE, 1, SEC_CODE_OPT_FOR_OPEN); SetCell(t_id, BUTTON_DOWN_STRIKE_LINE, 1, "\\/ ");Green(BUTTON_DOWN_STRIKE_LINE, 1); SetCell(t_id, BUTTON_OPEN_LINE, 1, "ОТКРЫТЬ "); if MayOpen then Green(BUTTON_OPEN_LINE, 1); else Gray(BUTTON_OPEN_LINE, 1); end; SetCell(t_id, INFO_TITLE_LINE, 1, "Профит"); SetCell(t_id, INFO_TITLE_LINE, 2, "Кол-во откр."); SetCell(t_id, INFO_TITLE_LINE, 3, "Ср.цена поз."); SetCell(t_id, INFO_TITLE_LINE, 4, "Дата откр."); SetCell(t_id, OPT_PROFIT_LINE, 0, "ОПЦИОНЫ"); SetCell(t_id, OPT_PROFIT_LINE, 1, tostring(0)); SetCell(t_id, OPT_PROFIT_LINE, 2, tostring(OPEN_BALANCE_OPT)); SetCell(t_id, OPT_PROFIT_LINE, 3, tostring(OPEN_PRICE_OPT)); SetCell(t_id, OPT_PROFIT_LINE, 4, tostring(OPEN_DATE_OPT)); SetCell(t_id, FUT_PROFIT_LINE, 0, "ФЬЮЧЕРСЫ"); SetCell(t_id, FUT_PROFIT_LINE, 1, tostring(0)); SetCell(t_id, FUT_PROFIT_LINE, 2, tostring(OPEN_BALANCE_FUT)); SetCell(t_id, FUT_PROFIT_LINE, 3, tostring(OPEN_PRICE_FUT)); SetCell(t_id, FUT_PROFIT_LINE, 4, tostring(OPEN_DATE_FUT)); SetCell(t_id, ALL_PROFIT_LINE, 0, "ИТОГО") |
ПОСТАНОВКА И СНЯТИЕ STOP-ОРДЕРА В QLUA(LUA)
Когда передо мной встала задача удаления поставленного стоп-ордера, наткнулся в интернете на скудность информации по данной тематике.
Самая распространенная ошибка начинающего программиста отправка в SendTransaction в STOP_ORDER_KEY trans_id стоп-ордера
Робот выставляет стоп-заявку на покупку по определенной цене, затем через 2 секунды снимает её.
Также в коде имеются следующие фишки:
- Запись удобочитаемого лог-файла. Записи с интервалом <=1 сек. группируются в пул. Между пулами — пустая строка. После остановки скрипта в файл добавляется двойная линия.
- Функция преобразования числа в строку с удалением точки и нулей правее нее для отправки этой строки в SendTransaction
- Функция, возвращающая Entry или Exit в зависимости от trans_id принадлежности транзакций к входу или выходу
[Начало:LUA] --/*НАСТРАИВАЕМЫЕ ПАРАМЕТРЫ*/ NAME_OF_STRATEGY = 'Str1_' -- НАЗВАНИЕ СТРАТЕГИИ (не более 9 символов!) CLASS_CODE = "QJSIM" -- Код класса SPBFUT SEC_CODE = "SBER" -- Код бумаги SiZ6 SiH7, SiM7, SiU7 ACCOUNT = "NL0011100043" -- Идентификатор счета SPBFUT00355 CLIENT_CODE = NAME_OF_STRATEGY..SEC_CODE -- "Код клиента" QTY_LOTS = "1" -- Кол-во торгуемых лотов FILE_LOG_NAME = "C:\\TRADING\\QUIK Junior\\Scripts\\Log.txt" -- ИМЯ ЛОГ-ФАЙЛА --/*РАБОЧИЕ ПЕРЕМЕННЫЕ РОБОТА (менять не нужно)*/ g_price_step = 0 -- ШАГ ЦЕНЫ ИНСТРУМЕНТА g_trans_id_entry = 110001 -- Задает начальный номер ID транзакций на вход g_trans_id_exit = 220001 -- Задает начальный номер ID транзакций на выход g_arrTransId_entry = {} -- массив ID транзакций на вход g_arrTransId_exit = {} -- массив ID транзакций на выход g_transId_del_order = "1234" -- ID ордера на удаление заявки (не меняется) g_transId_del_stopOrder = "6789" -- ID ордера на удаление стоп заявки (не меняется) g_currentPosition = 0 -- в позиции? сколько лотов и какое направление g_IsTrallingStop = false -- выставлен ли трейлинг стоп на сервере g_stopOrderEntry_num= "" -- номер стоп-заявки на вход в системе, по которому её можно снять g_stopOrderExit_num = "" -- номер стоп-заявки на выход в системе, по которому её можно снять g_order_num = "" -- номер заявки в системе, по которому её можно снять g_oldTrade_num = "" -- номер предыдущей обработанной сделки g_previous_time = os.time() -- помещение в переменную времени сервера в формате HHMMSS isRun = true -- Флаг поддержания работы бесконечного цикла в main function OnInit() -- Получает ШАГ ЦЕНЫ ИНСТРУМЕНТА g_price_step = getParamEx(CLASS_CODE, SEC_CODE, "SEC_PRICE_STEP").param_value f = io.open(FILE_LOG_NAME, "a+") -- открывает файл myLog("Initialization finished") end function main() g_trans_id_exit = g_trans_id_exit + 1 g_arrTransId_exit[#g_arrTransId_exit+1] = g_trans_id_exit SendStopOrder("131.9", QTY_LOTS, "B", g_trans_id_exit) -- Отправить стоп ордер sleep(2000) -- через 2 секунды DeleteStopOrder(g_stopOrderExit_num) -- удалить стоп-ордер while isRun do sleep(5000) -- обрабатываем цикл с задержкой 5сек. end end -- функция вызывается терминалом ТЕРМИНАЛОМ QUIK при остановке скрипта function OnStop() myLog("Script Stoped") f:close() -- Закрывает файл isRun = false end function SendStopOrder(stopPrice, quantity, operation, trans_id) local offset=50 -- отступ для гарантированного исполнения ордера по рынку (в кол-ве шагов цены) local price local direction if operation=="B" then price = stopPrice + g_price_step*offset direction = "5" -- Направленность стоп-цены. «5» - больше или равно else price = stopPrice - g_price_step*offset direction = "4" -- Направленность стоп-цены. «4» - меньше или равно end --message("stopPrice"..stopPrice) --Пошлем стоп заявку local Transaction = { ['ACTION'] = "NEW_STOP_ORDER", ['PRICE'] = tostring(price), ['EXPIRY_DATE'] = "TODAY",--"GTC", -- на учебном серве только стоп-заявки с истечением сегодня, потом поменять на GTC ['STOPPRICE'] = tostring(stopPrice), ['STOP_ORDER_KIND'] = "SIMPLE_STOP_ORDER", ['TRANS_ID'] = removeZero(tostring(trans_id)), ['CLASSCODE'] = CLASS_CODE, ['SECCODE'] = SEC_CODE, ['ACCOUNT'] = ACCOUNT, ['CLIENT_CODE'] = CLIENT_CODE, -- Комментарий к транзакции, который будет виден в транзакциях, заявках и сделках ['TYPE'] = "L", ['OPERATION'] = tostring(operation), ['CONDITION'] = direction, -- Направленность стоп-цены. Возможные значения: «4» - меньше или равно, «5» – больше или равно ['QUANTITY'] = tostring(math.abs(quantity)) } local res = sendTransaction(Transaction) if string.len(res) ~= 0 then message('Error: '..res, 3) myLog("SendStopOrder(): Error "..res) else myLog("SendStopOrder(): "..EntryOrExit(trans_id).."; trans_id="..trans_id) end end function DeleteStopOrder(stopOrder_num) local Transaction = { ['ACTION'] = "KILL_STOP_ORDER", ['CLASSCODE'] = CLASS_CODE, ['SECCODE'] = SEC_CODE, ['ACCOUNT'] = ACCOUNT, ['CLIENT_CODE'] = CLIENT_CODE, ['TYPE'] = "L", ['STOP_ORDER_KIND'] = "SIMPLE_STOP_ORDER", ['TRANS_ID'] = g_transId_del_order, -- ID УДАЛЯЮЩЕЙ транзакции ['STOP_ORDER_KEY'] = tostring(stopOrder_num) } local res = sendTransaction(Transaction) if string.len(res) ~= 0 then message('Error: '..res, 3) myLog("DeleteStopOrder(): fail "..res) else myLog("DeleteStopOrder(): "..stopOrder_num.." success ") end end -- создан/изменен/сработал стоп-ордер function OnStopOrder(stopOrder) -- Если не относится к роботу, выходит из функции if stopOrder.brokerref:find(CLIENT_CODE) == nil then return end local string state="_" -- состояние заявки --бит 0 (0x1) Заявка активна, иначе не активна if bit.band(stopOrder.flags,0x1)==0x1 then state="стоп-заявка создана" if EntryOrExit(stopOrder.trans_id) == "Entry" then g_stopOrderEntry_num = stopOrder.order_num end if EntryOrExit(stopOrder.trans_id) == "Exit" then g_stopOrderExit_num = stopOrder.order_num end end if bit.band(stopOrder.flags,0x2)==0x1 or stopOrder.flags==26 then state="стоп-заявка снята" end if bit.band(stopOrder.flags,0x2)==0x0 and bit.band(stopOrder.flags,0x1)==0x0 then state="стоп-ордер исполнен" end if bit.band(stopOrder.flags,0x400)==0x1 then state="стоп-заявка сработала, но была отвергнута торговой системой" end if bit.band(stopOrder.flags,0x800)==0x1 then state="стоп-заявка сработала, но не прошла контроль лимитов" end if state=="_" then state="Набор битовых флагов="..tostring(stopOrder.flags) end myLog("OnStopOrder(): sec_code="..stopOrder.sec_code.."; "..EntryOrExit(stopOrder.trans_id)..";\t"..state.. "; condition_price="..stopOrder.condition_price.."; transID="..stopOrder.trans_id.."; order_num="..stopOrder.order_num ) end ------------------------- Сервисные функции-------------------- -- функция записывает в лог строчку с временем и датой function myLog(str) if f==nil then return end local current_time=os.time()--tonumber(timeformat(getInfoParam("SERVERTIME"))) -- помещене в переменную времени сервера в формате HHMMSS if (current_time-g_previous_time)>1 then -- если текущая запись произошла позже 1 секунды, чем предыдущая f:write("\n") -- добавляем пустую строку для удобства чтения end g_previous_time = current_time f:write(os.date().."; ".. str .. ";\n") if str:find("Script Stoped") ~= nil then f:write("======================================================================================================================\n\n") f:write("======================================================================================================================\n") end f:flush() -- Сохраняет изменения в файле end -- удаление точки и нулей после нее function removeZero(str) while (string.sub(str,-1) == "0" and str ~= "0") do str = string.sub(str,1,-2) end if (string.sub(str,-1) == ".") then str = string.sub(str,1,-2) end return str end -- Возвращает Entry или Exit в зависимости от trans_id function EntryOrExit(trans_id) if isArrayContain(g_arrTransId_entry, trans_id) then return "Entry" elseif isArrayContain(g_arrTransId_exit, trans_id) then return "Exit" elseif trans_id==g_transId_del_order then return "DeleteOrder" elseif trans_id==g_transId_del_stopOrder then return "DeleteStopOrder" else return "NoId" end end function isArrayContain(array, value) if #array < 1 then return end for i=1, #array do if array[i]==value then return true end end return false end [Конец:LUA]
В итоге получается текстовый лог-файл, удобный для чтения человеком, с которого можно считать данные программой (например на C#) и подбить статистику:
01/17/17 19:34:56; Initialization finished;
01/17/17 19:34:56; SendStopOrder(): Exit; trans_id=220002;
01/17/17 19:34:56; OnStopOrder(): sec_code=SBER; Exit; стоп-заявка создана; condition_price=131.9; transID=220002; order_num=7902164;
01/17/17 19:34:58; DeleteStopOrder(): 7902164 success ;
01/17/17 19:34:58; OnStopOrder(): sec_code=SBER; Exit; стоп-заявка снята; condition_price=131.9; transID=220002; order_num=7902164;
01/17/17 19:35:01; Script Stoped;
======================================================================================================================
======================================================================================================================
[Конец: Log.txt]
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 | --[[ Простой MA-робот (c)QuikLuaCSharp.ru !!! ДЛЯ ИСПОЛЬЗОВАНИЯ ТОЛЬКО В ОБРАЗОВАТЕЛЬНЫХ ЦЕЛЯХ НА ДЕМО-СЧЕТЕ !!! Робот торгует по 2-м простым(simple) скользящим средним (MA). 1.Если нет открытых роботом позиций выполняется следующий алгоритм: 1.Если быстрая скользящая пересекает медленную снизу вверх и на текущей свече еще не открывались позиции, совершается покупка. 2.Если быстрая скользящая пересекает медленную сверху вниз и на текущей свече еще не открывались позиции, совершается продажа. 2.Если открылась позиция, выставляется "Тэйк-профит и Стоп-лимит" 3.Робот ждет, пока закроется позичия по Стоп-лоссу, либо Тэйк-профиту, после чего переходит к пункту №1 Особенности: 1.Робот выводит в текстовых сообщениях информацию о ключевых моментах алгоритма, у всех его сообщений префикс "Простой MA-робот:". 2.Робот всегда находится в 1-м из 2-х состояний(ROBOT_STATE):'В ПРОЦЕССЕ СДЕЛКИ', либо 'В ПОИСКЕ ТОЧКИ ВХОДА'. 3.Если при выставлении заявки на продажу робот узнает, что операции шорт запрещены по данному инструменту, он больше не будет открывать шорт, только лонг. 4.Когда робот получает сигнал на открытие сделки, он совершает 10 попыток с прмежутками в 100 мс открыть позицию, если этого не удается, останавливает скрипт. 5.После открытия позиции робот совершает 10 попыток с прмежутками в 100 мс выставить Тэйк-профит и Стоп-лимит, а затем дождаться закрытия позиции, если этого не удается, останавливает скрипт. 6.Если стоп-заявка сработала, но позиция не закрылась в течении 10 секунд, пытается за 10 попыток принудительно закрыть позицию встречной сделкой. Если позицию удалось закрыть (даже принудительно), продолжает работать, иначе скрипт останавливается. ]] --/*НАСТРАИВАЕМЫЕ ПАРАМЕТРЫ*/ --ACCOUNT = 'NL0011100043'; -- Идентификатор счета ACCOUNT = 'SPBFUT00506'; -- Идентификатор счета --CLASS_CODE = 'QJSIM'; -- Код класса CLASS_CODE = 'SPBFUT'; -- Код класса --SEC_CODE = 'SBER'; -- Код бумаги SEC_CODE = 'RIZ5'; -- Код бумаги INTERVAL = INTERVAL_M1; -- Таймфрейм графика (для построения скользящих) SLOW_MA_PERIOD = 12; -- ПЕРИОД МЕДЛЕННОЙ скользящей SLOW_MA_SOURCE = 'C'; -- ИСТОЧНИК МЕДЛЕННОЙ скользящей [O - open, C - close, H - hi, L - low] FAST_MA_PERIOD = 4; -- ПЕРИОД БЫСТРОЙ скользящей FAST_MA_SOURCE = 'C'; -- ИСТОЧНИК БЫСТРОЙ скользящей [O - open, C - close, H - hi, L - low] STOP_LOSS = 10; -- Размер СТОП-ЛОССА (в шагах цены) TAKE_PROFIT = 30; -- Размер ТЭЙК-ПРОФИТА (в шагах цены) --/*РАБОЧИЕ ПЕРЕМЕННЫЕ РОБОТА (менять не нужно)*/ SEC_PRICE_STEP = 0; -- ШАГ ЦЕНЫ ИНСТРУМЕНТА SEC_NO_SHORT = false; -- Флаг, что по данному инструменту запрещены операции шорт DS = nil; -- Источник данных графика (DataSource) ROBOT_STATE ='В ПОИСКЕ ТОЧКИ ВХОДА';-- СОСТОЯНИЕ робота ['В ПРОЦЕССЕ СДЕЛКИ', либо 'В ПОИСКЕ ТОЧКИ ВХОДА'] trans_id = os.time(); -- Задает начальный номер ID транзакций trans_Status = nil; -- Статус текущей транзакции из функции OnTransPeply trans_result_msg = ''; -- Сообщение по текущей транзакции из функции OnTransPeply CurrentDirect = 'BUY'; -- Текущее НАПРАВЛЕНИЕ ['BUY', или 'SELL'] LastOpenBarIndex = 0; -- Индекс свечи, на которой была открыта последняя позиция (нужен для того, чтобы после закрытия по стопу тут же не открыть еще одну позицию) Run = true; -- Флаг поддержания работы бесконечного цикла в main -- Функция первичной инициализации скрипта (ВЫЗЫВАЕТСЯ ТЕРМИНАЛОМ QUIK в самом начале) function OnInit() -- Получает доступ к свечам графика local Error = ''; DS,Error = CreateDataSource(CLASS_CODE, SEC_CODE, INTERVAL); -- Если график, к которому нужно подключиться не открыт в терминале, то данные заказываются с сервера, на их получение нужно время, -- по этому, рекомендуется добавлять вот такое ожидание, прежде, чем обращаться к DS: -- Ждет, пока данные будут получены с сервера (на случай, если такой график не открыт) 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() -- Получает ШАГ ЦЕНЫ ИНСТРУМЕНТА SEC_PRICE_STEP = getParamEx(CLASS_CODE, SEC_CODE, "SEC_PRICE_STEP").param_value; end; function main() -- Выводит сообщение message('Простой MA-робот: '..ROBOT_STATE); -- "Бесконечный" цикл while Run do --Если СОСТОЯНИЕ робота "В ПРОЦЕССЕ СДЕЛКИ" if ROBOT_STATE == 'В ПРОЦЕССЕ СДЕЛКИ' then -- Выводит сообщение message('Простой MA-робот: В ПРОЦЕССЕ СДЕЛКИ'); -- Делает 10 попыток открыть сделку local Price = false; -- Переменная для получения результата открытия позиции (цена, либо ошибка(false)) for i=1,10 do if not Run then return; end; -- Если скрипт останавливается, не затягивает процесс -- Если первый раз пытается открыть SELL, а операции шорт по данному инструменту запрещены if CurrentDirect == "SELL" and SEC_NO_SHORT then -- Прерывает цикл FOR break; end; -- Совершает СДЕЛКУ указанного типа ["BUY", или "SELL"] по рыночной(текущей) цене размером в 1 лот, --- возвращает цену открытой сделки, либо FALSE, если невозможно открыть сделку Price = Trade(CurrentDirect); -- Если сделка открылась if Price ~= false then -- Прерывает цикл FOR break; end; sleep(100); -- Пауза в 100 мс между попытками открыть сделку end; if not Run then return; end; -- Если скрипт останавливается, не затягивает процесс -- Если сделка открыта if Price ~= false then -- Запоминает индекс свечи, на которой была открыта последняя позиция (нужен для того, чтобы после закрытия по стопу тут же не открыть еще одну позицию) LastOpenBarIndex = DS:Size(); -- Выводит сообщение message('Простой MA-робот: Открыта сделка '..CurrentDirect..' по цене '..Price); -- Делает 10 попыток выставить СТОП-ЛОСС и ТЭЙК-ПРОФИТ, и дождаться закрытия сделки message('Простой MA-робот: Делает 10 попыток выставить СТОП-ЛОСС и ТЭЙК-ПРОФИТ, и дождаться закрытия сделки'); local Result = nil; -- Переменная для получения результата выставления и срабатывания СТОП-ЛОСС и ТЭЙК-ПРОФИТ for i=1,10 do if not Run then return; end; -- Если скрипт останавливается, не затягивает процесс -- Выставляет СТОП-ЛОСС и ТЭЙК-ПРОФИТ, ЖДЕТ пока он сработает, принимает ЦЕНУ и ТИП ["BUY", или "SELL"] открытой сделки, --- возвращает FALSE, если не удалось выставить СТОП-ЛОСС и ТЭЙК-ПРОФИТ, или TRUE, если сделка закрылась, --- либо NIL, если при ошибке за 10 попыток не удалось принудительно закрыть позицию Result = SL_TP(Price, CurrentDirect); -- Если сделка закрылась if Result == true then -- Прерывает цикл FOR break; end; end; -- Если за 10 попыток не удалось закрыть позицию if Result == nil or Result == false then -- Выводит сообщение message('Простой MA-робот: После 10-и попыток не удалось закрыть сделку!!! Завершение скрипта!!!'); -- Завершает выполнение скрипта Run = false; -- Прерывает основной цикл WHILE break; else -- УДАЛОСЬ ЗАКРЫТЬ СДЕЛКУ -- Выводит сообщение message('Простой MA-робот: Сделка закрыта по СТОП-ЛОССУ, либо ТЭЙК-ПРОФИТУ'); --Меняет СОСТОЯНИЕ робота на "В ПОИСКЕ ТОЧКИ ВХОДА" ROBOT_STATE = 'В ПОИСКЕ ТОЧКИ ВХОДА'; -- Выводит сообщение message('Простой MA-робот: В ПОИСКЕ ТОЧКИ ВХОДА'); end; else -- Сделку не удалось открыть -- Если первый раз пытался открыть SELL, а операции шорт по данному инструменту запрещены if CurrentDirect == "SELL" and SEC_NO_SHORT then -- Выводит сообщение message('Простой MA-робот: Была первая попытка совершить запрещенную операцию шорт! Больше этого не повторится:)'); --Меняет СОСТОЯНИЕ робота на "В ПОИСКЕ ТОЧКИ ВХОДА" ROBOT_STATE = 'В ПОИСКЕ ТОЧКИ ВХОДА'; -- Выводит сообщение message('Простой MA-робот: В ПОИСКЕ ТОЧКИ ВХОДА'); else -- Выводит сообщение message('Простой MA-робот: После 10-и попыток не удалось открыть сделку!!! Завершение скрипта!!!'); -- Завершает выполнение скрипта Run = false; end; end; else -- СОСТОЯНИЕ робота 'В ПОИСКЕ ТОЧКИ ВХОДА' -- Если на этой свече еще не было открыто позиций if DS:Size() > LastOpenBarIndex then -- Если быстрая пересекла медленную СНИЗУ ВВЕРХ if FastMA(DS:Size()-1) <= SlowMA(DS:Size()-1) and FastMA() > SlowMA() then -- Задает направление НА ПОКУПКУ CurrentDirect = 'BUY'; message('CurrentDirect = "BUY"'); -- Меняет СОСТОЯНИЕ робота на "В ПРОЦЕССЕ СДЕЛКИ" ROBOT_STATE = 'В ПРОЦЕССЕ СДЕЛКИ'; -- Если быстрая пересекла медленную СВЕРХУ ВНИЗ elseif FastMA(DS:Size()-1) >= SlowMA(DS:Size()-1) and FastMA() < SlowMA() then -- Если по данному инструменту не запрещены операции шорт if not SEC_NO_SHORT then -- Задает направление НА ПРОДАЖУ CurrentDirect = 'SELL'; message('CurrentDirect = "SELL"'); -- Меняет СОСТОЯНИЕ робота на "В ПРОЦЕССЕ СДЕЛКИ" ROBOT_STATE = 'В ПРОЦЕССЕ СДЕЛКИ'; end; end; end; end; sleep(10);--Пауза 10 мс, для того, чтобы не перегружать процессор компьютера end; end; -- Функция вызывается терминалом QUIK при получении ответа на транзакцию пользователя function OnTransReply(trans_reply) -- Если поступила информация по текущей транзакции if trans_reply.trans_id == trans_id then -- Передает статус в глобальную переменную trans_Status = trans_reply.status; -- Передает сообщение в глобальную переменную trans_result_msg = trans_reply.result_msg; end; end; -- Функция ВЫЗЫВАЕТСЯ ТЕРМИНАЛОМ QUIK при остановке скрипта function OnStop() Run = false; end; ----------------------------- -- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ -- ----------------------------- -- Совершает СДЕЛКУ указанного типа (Type) ["BUY", или "SELL"] по рыночной(текущей) цене размером в 1 лот, --- возвращает цену открытой сделки, либо FALSE, если невозможно открыть сделку function Trade(Type) --Получает ID транзакции trans_id = trans_id + 1; local Price = 0; local Operation = ''; --Устанавливает цену и операцию, в зависимости от типа сделки и от класса инструмента if Type == 'BUY' then if CLASS_CODE ~= 'QJSIM' and CLASS_CODE ~= 'TQBR' then Price = getParamEx(CLASS_CODE, SEC_CODE, 'offer').param_value + 10*SEC_PRICE_STEP;end; -- по цене, завышенной на 10 мин. шагов цены Operation = 'B'; else if CLASS_CODE ~= 'QJSIM' and CLASS_CODE ~= 'TQBR' then Price = getParamEx(CLASS_CODE, SEC_CODE, 'bid').param_value - 10*SEC_PRICE_STEP;end; -- по цене, заниженной на 10 мин. шагов цены Operation = 'S'; end; -- Заполняет структуру для отправки транзакции local Transaction={ ['TRANS_ID'] = tostring(trans_id), ['ACTION'] = 'NEW_ORDER', ['CLASSCODE'] = CLASS_CODE, ['SECCODE'] = SEC_CODE, ['OPERATION'] = Operation, -- операция ("B" - buy, или "S" - sell) ['TYPE'] = 'M', -- по рынку (MARKET) ['QUANTITY'] = '1', -- количество ['ACCOUNT'] = ACCOUNT, ['PRICE'] = tostring(Price), ['CLIENT_CODE']= 'Простой MA-робот' } -- Отправляет транзакцию sendTransaction(Transaction); -- Ждет, пока получит статус текущей транзакции (переменные "trans_Status" и "trans_result_msg" заполняются в функции OnTransReply()) while Run and trans_Status == nil do sleep(1); end; -- Запоминает значение local Status = trans_Status; -- Очищает глобальную переменную trans_Status = nil; -- Если транзакция не выполнена по какой-то причине if Status ~= 3 then -- Если данный инструмент запрещен для операции шорт if Status == 6 then -- Выводит сообщение message('Простой MA-робот: Данный инструмент запрещен для операции шорт!'); SEC_NO_SHORT = true; else -- Выводит сообщение с ошибкой message('Простой MA-робот: Транзакция не прошла!\nОШИБКА: '..trans_result_msg); end; -- Возвращает FALSE return false; else --Транзакция отправлена local OrderNum = nil; --ЖДЕТ пока ЗАЯВКА на ОТКРЫТИЕ сделки будет ИСПОЛНЕНА полностью --Запоминает время начала в секундах local BeginTime = os.time(); while Run and OrderNum == nil do --Перебирает ТАБЛИЦУ ЗАЯВОК for i=0,getNumberOf('orders')-1 do local order = getItem('orders', i); --Если заявка по отправленной транзакции ИСПОЛНЕНА ПОЛНОСТЬЮ if order.trans_id == trans_id and order.balance == 0 then --Запоминает номер заявки OrderNum = order.order_num; --Прерывает цикл FOR break; end; end; --Если прошло 10 секунд, а заявка не исполнена, значит произошла ошибка if os.time() - BeginTime > 9 then -- Выводит сообщение с ошибкой message('Простой MA-робот: Прошло 10 секунд, а заявка не исполнена, значит произошла ошибка'); -- Возвращает FALSE return false; end; sleep(10); -- Пауза 10 мс, чтобы не перегружать процессор компьютера end; --ЖДЕТ пока СДЕЛКА ОТКРЫТИЯ позиции будет СОВЕРШЕНА --Запоминает время начала в секундах BeginTime = os.time(); while Run do --Перебирает ТАБЛИЦУ СДЕЛОК for i=0,getNumberOf('trades')-1 do local trade = getItem('trades', i); --Если сделка по текущей заявке if trade.order_num == OrderNum then --Возвращает фАКТИЧЕСКУЮ ЦЕНУ открытой сделки return trade.price; end; end; --Если прошло 10 секунд, а сделка не совершена, значит на демо-счете произошла ошибка if os.time() - BeginTime > 9 then -- Выводит сообщение с ошибкой message('Простой MA-робот: Прошло 10 секунд, а сделка не совершена, значит на демо-счете произошла ошибка'); -- Возвращает FALSE return false; end; sleep(10); -- Пауза 10 мс, чтобы не перегружать процессор компьютера end; end; end; -- ПРИНУДИТЕЛЬНО ЗАКРЫВАЕТ ОТКРЫТУЮ ПОЗИЦИЮ переданного типа (Type) ["BUY", или "SELL"] function KillPos(Type) -- Дается 10 попыток local Count = 0; -- Счетчик попыток if Type == 'BUY' then -- Пока скрипт не остановлен и позиция не закрыта while Run and not Trade('SELL') do -- Открывает SELL, тем самым закрывая BUY, если Trade('SELL') вернет TRUE, цикл прекратится Count = Count + 1; -- Увеличивает счетчик -- Если за 10 попыток не удалось закрыть позицию if Count == 10 then -- Возвращает NIL return nil; end; sleep(100); -- Пауза 100 мс, чтобы изменилась ситуация на сервере end; else -- Пока скрипт не остановлен и позиция не закрыта while Run and not Trade('BUY') do -- Открывает BUY, тем самым закрывая SELL, если Trade('BUY') вернет TRUE, цикл прекратится Count = Count + 1; -- Увеличивает счетчик -- Если за 10 попыток не удалось закрыть позицию if Count == 10 then -- Возвращает NIL return nil; end; sleep(100); -- Пауза 100 мс, чтобы изменилась ситуация на сервере end; end; -- Возвращает TRUE, если удалось принудительно закрыть позицию return true; end; -- Выставляет СТОП-ЛОСС и ТЭЙК-ПРОФИТ, ЖДЕТ пока он сработает, принимает ЦЕНУ (Price) и ТИП (Type) ["BUY", или "SELL"] открытой сделки, --- возвращает FALSE, если не удалось выставить СТОП-ЛОСС и ТЭЙК-ПРОФИТ, либо TRUE, если сделка закрылась, --- либо NIL, если при ошибке за 10 попыток не удалось принудительно закрыть позицию function SL_TP(Price, Type) -- ID транзакции trans_id = trans_id + 1; -- Находит направление для заявки local operation = ""; local price = "0"; -- Цена, по которой выставится заявка при срабатывании Стоп-Лосса (для рыночной заявки по акциям должна быть 0) local stopprice = ""; -- Цена Тэйк-Профита local stopprice2 = ""; -- Цена Стоп-Лосса local market = "YES"; -- После срабатывания Тэйка, или Стопа, заявка сработает по рыночной цене -- Если открыт BUY, то направление стоп-лосса и тэйк-профита SELL, иначе направление стоп-лосса и тэйк-профита BUY if Type == 'BUY' then operation = "S"; -- Тэйк-профит и Стоп-лосс на продажу(чтобы закрыть BUY, нужно открыть SELL) -- Если не акции if CLASS_CODE ~= 'QJSIM' and CLASS_CODE ~= 'TQBR' then price = tostring(math.floor(getParamEx(CLASS_CODE, SEC_CODE, 'PRICEMIN').param_value)); -- Цена выставляемой заявки после страбатывания Стопа минимально возможная, чтобы не проскользнуло market = "NO"; -- После срабатывания Тэйка, или Стопа, заявка сработает НЕ по рыночной цене end; stopprice = tostring(Price + TAKE_PROFIT*SEC_PRICE_STEP); -- Уровень цены, когда активируется Тэйк-профит stopprice2 = tostring(Price - STOP_LOSS*SEC_PRICE_STEP); -- Уровень цены, когда активируется Стоп-лосс else -- открыт SELL operation = "B"; -- Тэйк-профит и Стоп-лосс на покупку(чтобы закрыть SELL, нужно открыть BUY) -- Если не акции if CLASS_CODE ~= 'QJSIM' and CLASS_CODE ~= 'TQBR' then price = tostring(math.floor(getParamEx(CLASS_CODE, SEC_CODE, 'PRICEMAX').param_value)); -- Цена выставляемой заявки после страбатывания Стопа максимально возможная, чтобы не проскользнуло market = "NO"; -- После срабатывания Тэйка, или Стопа, заявка сработает НЕ по рыночной цене end; stopprice = tostring(Price - TAKE_PROFIT*SEC_PRICE_STEP); -- Уровень цены, когда активируется Тэйк-профит stopprice2 = tostring(Price + STOP_LOSS*SEC_PRICE_STEP); -- Уровень цены, когда активируется Стоп-лосс end; -- Заполняет структуру для отправки транзакции на Стоп-лосс и Тэйк-профит local Transaction = { ["ACTION"] = "NEW_STOP_ORDER", -- Тип заявки ["TRANS_ID"] = tostring(trans_id), ["CLASSCODE"] = CLASS_CODE, ["SECCODE"] = SEC_CODE, ["ACCOUNT"] = ACCOUNT, ["OPERATION"] = operation, -- Операция ("B" - покупка(BUY), "S" - продажа(SELL)) ["QUANTITY"] = "1", -- Количество в лотах ["PRICE"] = price, -- Цена, по которой выставится заявка при срабатывании Стоп-Лосса (для рыночной заявки по акциям должна быть 0) ["STOPPRICE"] = stopprice, -- Цена Тэйк-Профита ["STOP_ORDER_KIND"] = "TAKE_PROFIT_AND_STOP_LIMIT_ORDER", -- Тип стоп-заявки ["EXPIRY_DATE"] = "TODAY", -- Срок действия стоп-заявки ("GTC" – до отмены,"TODAY" - до окончания текущей торговой сессии, Дата в формате "ГГММДД") -- "OFFSET" - (ОТСТУП)Если цена достигла Тэйк-профита и идет дальше в прибыль, -- то Тэйк-профит сработает только когда цена вернется минимум на 2 шага цены назад, -- это может потенциально увеличить прибыль ["OFFSET"] = tostring(2*SEC_PRICE_STEP), ["OFFSET_UNITS"] = "PRICE_UNITS", -- Единицы измерения отступа ("PRICE_UNITS" - шаг цены, или "PERCENTS" - проценты) -- "SPREAD" - Когда сработает Тэйк-профит, выставится заявка по цене хуже текущей на 100 шагов цены, -- которая АВТОМАТИЧЕСКИ УДОВЛЕТВОРИТСЯ ПО ТЕКУЩЕЙ ЛУЧШЕЙ ЦЕНЕ, -- но то, что цена значительно хуже, спасет от проскальзывания, -- иначе, сделка может просто не закрыться (заявка на закрытие будет выставлена, но цена к тому времени ее уже проскочит) ["SPREAD"] = tostring(100*SEC_PRICE_STEP), ["SPREAD_UNITS"] = "PRICE_UNITS", -- Единицы измерения защитного спрэда ("PRICE_UNITS" - шаг цены, или "PERCENTS" - проценты) -- "MARKET_TAKE_PROFIT" = ("YES", или "NO") должна ли выставится заявка по рыночной цене при срабатывании Тэйк-Профита. -- Для рынка FORTS рыночные заявки, как правило, запрещены, -- для лимитированной заявки на FORTS нужно указывать заведомо худшую цену, чтобы она сработала сразу же, как рыночная ["MARKET_TAKE_PROFIT"] = market, ["STOPPRICE2"] = stopprice2, -- Цена Стоп-Лосса ["IS_ACTIVE_IN_TIME"] = "NO", -- "MARKET_TAKE_PROFIT" = ("YES", или "NO") должна ли выставится заявка по рыночной цене при срабатывании Стоп-Лосса. -- Для рынка FORTS рыночные заявки, как правило, запрещены, -- для лимитированной заявки на FORTS нужно указывать заведомо худшую цену, чтобы она сработала сразу же, как рыночная ["MARKET_STOP_LIMIT"] = market, ["CLIENT_CODE"] = "Простой MA-робот ТЭЙК-ПРОФИТ и СТОП-ЛОСС" } -- Отправляет транзакцию на установку ТЭЙК-ПРОФИТ и СТОП-ЛОСС sendTransaction(Transaction); -- Ждет, пока не получит статус текущей транзакции (переменные "trans_Status" и "trans_result_msg" заполняются в функции OnTransReply()) while Run and trans_Status == nil do sleep(10); end; -- Запоминает значение local Status = trans_Status; -- Очищает глобальную переменную trans_Status = nil; -- Если транзакция не выполнена по какой-то причине if Status ~= 3 then -- Выводит сообщение с ошибкой message('Простой MA-робот: Установка ТЭЙК-ПРОФИТ и СТОП-ЛОСС не удалась!\nОШИБКА: '..trans_result_msg); -- Возвращает FALSE return false; else -- Выводит сообщение message('Простой MA-робот: ВЫСТАВЛЕНА заявка ТЭЙК-ПРОФИТ и СТОП-ЛОСС: '..trans_id); local OrderNum_CLOSE = nil; -- ЖДЕТ пока СТОП-ЗАЯВКА на СТОП-ЛОСС и ТЭЙК-ПРОФИТ будет ИСПОЛНЕНА полностью while Run and OrderNum_CLOSE == nil do -- Перебирает ТАБЛИЦУ СТОП-ЗАЯВОК for i=0,getNumberOf("stop_orders")-1 do local stop_order = getItem("stop_orders", i); -- Если заявка по текущей транзакции СТОП-ЛОСС и ТЭЙК-ПРОФИТ if stop_order.trans_id == trans_id then -- Если заявка по отправленной СТОП-ЛОСС и ТЭЙК-ПРОФИТ транзакции ИСПОЛНЕНА ПОЛНОСТЬЮ if stop_order.balance == 0 then -- Если по наступлению стоп-цены выставлена заявка if stop_order.linkedorder > 0 then -- Запоминает номер заявки, которая была создана при срабатывании СТОП-ЛОСС, или ТЭЙК-ПРОФИТ OrderNum_CLOSE = stop_order.linkedorder; -- Прерывает цикл FOR break; -- Стоп-заявка сработала, но была отвергнута торговой системой elseif CheckBit(stop_order.flags, 10) == 1 then -- ПРИНУДИТЕЛЬНО ЗАКРЫВАЕТ ОТКРЫТУЮ ПОЗИЦИЮ и выходит из функции return KillPos(Type); end; end; end; end; sleep(10); -- Пауза 10 мс, чтобы не перегружать процессор компьютера end; --ЖДЕТ пока СДЕЛКА ЗАКРЫТИЯ позиции будет СОВЕРШЕНА --Запоминает время начала в секундах BeginTime = os.time(); while Run do --Перебирает ТАБЛИЦУ СДЕЛОК for i=0,getNumberOf("trades")-1 do local trade = getItem("trades", i); --Если сделка по текущей заявке на СТОП-ЛОСС и ТЭЙК-ПРОФИТ if trade.order_num == OrderNum_CLOSE then -- Возвращает TRUE return true; end; end; --Если прошло 10 секунд, а сделка не совершена, значит на демо-счете произошла ошибка сервера "Обработка кросс-заявок блокирована" if os.time() - BeginTime > 9 then -- ПРИНУДИТЕЛЬНО ЗАКРЫВАЕТ ОТКРЫТУЮ ПОЗИЦИЮ и выходит из функции return KillPos(Type); end; sleep(1); end; end; end; -- Возвращает ЗНАЧЕНИЕ МЕДЛЕННОЙ скользящей по индексу свечи (по умолчанию: последняя) function SlowMA(Index) -- Если индекс свечи не указан, то устанавливает индекс последней свечи if Index == nil then Index = DS:Size(); end; -- Сумма значений SLOW_MA_SOURCE на SLOW_MA_PERIOD свечах local Sum = 0; -- Перебирает последние SLOW_MA_PERIOD свечей for i=Index, Index - (SLOW_MA_PERIOD - 1), -1 do -- Считает сумму, исходя из выбранного источника для медленной скользящей if SLOW_MA_SOURCE == 'O' then Sum = Sum + DS:O(i); elseif SLOW_MA_SOURCE == 'C' then Sum = Sum + DS:C(i); elseif SLOW_MA_SOURCE == 'H' then Sum = Sum + DS:H(i); elseif SLOW_MA_SOURCE == 'L' then Sum = Sum + DS:L(i); else message('Простой MA-робот:ОШИБКА! Не верно указан источник для медленной скользящей!'); -- Останавливает скрипт OnStop(); end; end; -- Возвращает значение return Sum/SLOW_MA_PERIOD; end; -- Возвращает ЗНАЧЕНИЕ БЫСТРОЙ скользящей по индексу свечи (по умолчанию: последняя) function FastMA(Index) -- Если индекс свечи не указан, то устанавливает индекс последней свечи if Index == nil then Index = DS:Size(); end; -- Сумма значений FAST_MA_SOURCE на FAST_MA_PERIOD свечах local Sum = 0; -- Перебирает последние FAST_MA_PERIOD свечей for i=Index, Index - (FAST_MA_PERIOD - 1), -1 do -- Считает сумму, исходя из выбранного источника для быстрой скользящей if FAST_MA_SOURCE == 'O' then Sum = Sum + DS:O(i); elseif FAST_MA_SOURCE == 'C' then Sum = Sum + DS:C(i); elseif FAST_MA_SOURCE == 'H' then Sum = Sum + DS:H(i); elseif FAST_MA_SOURCE == 'L' then Sum = Sum + DS:L(i); else message('Простой MA-робот:ОШИБКА! Не верно указан источник для быстрой скользящей!'); -- Останавливает скрипт OnStop(); end; end; -- Возвращает значение return Sum/FAST_MA_PERIOD; end; -- Функция возвращает значение бита (число 0, или 1) под номером bit (начинаются с 0) в числе flags, если такого бита нет, возвращает nil function CheckBit(flags, bit) -- Проверяет, что переданные аргументы являются числами if type(flags) ~= "number" then error("Предупреждение!!! Checkbit: 1-й аргумент не число!"); end; if type(bit) ~= "number" then error("Предупреждение!!! Checkbit: 2-й аргумент не число!"); end; local RevBitsStr = ""; -- Перевернутое (задом наперед) строковое представление двоичного представления переданного десятичного числа (flags) local Fmod = 0; -- Остаток от деления local Go = true; -- Флаг работы цикла while Go do Fmod = math.fmod(flags, 2); -- Остаток от деления flags = math.floor(flags/2); -- Оставляет для следующей итерации цикла только целую часть от деления RevBitsStr = RevBitsStr ..tostring(Fmod); -- Добавляет справа остаток от деления if flags == 0 then Go = false; end; -- Если был последний бит, завершает цикл end; -- Возвращает значение бита local Result = RevBitsStr :sub(bit+1,bit+1); if Result == "0" then return 0; elseif Result == "1" then return 1; else return nil; end; end; |
Гайд Написание Lua скриптов для MoonLoader | BLASTHACK
Данная тема посвящена нюансам и особенностям разработки Lua скриптов для MoonLoader.
Список вопросов:
Начало разработки.
Взаимодействие с игроками.
Работа с переменными.
Работа с командами.
Начало разработки.
Каждый Lua скрипт при запуске создает поток «main» который и является основным местом работы со скриптом.
Сразу обговорим — скрипт «жив» пока «жив» поток main(), поэтому если мы хотим продолжать работать со скриптом после запуска — будем держать поток активным:
function main() -- Объявляем поток main()
wait(-1) -- Устанавливаем бесконечное ожидание
end -- закрываем функцию
Запускаем скрипт — работает!Что ж, поставим себе цель — при запуске выводить сообщение «Привет мир» в чат.
Что нам для этого нужно?
Заходим на WIKI (Раздел «Функции» moonloader`a) и ищем функции чата — Поиск -> «Chat». Видим функцию sampAddChatMessage(message, color) — то что надо.
Вставляем:
function main()
sampAddChatMessage("Привет мир!", 0xFFFFFFFF) -- Выводим сообщение в чат
wait(-1) -- Устанавливаем бесконечное ожидание
end
Запускаем скрипт — ошибка?(error) test.lua: opcode '0AF8' call caused an unhandled exception
Бежим на WIKI (Опять раздел «Функции» moonloader`a) и в поиск вводим наш опкод «0AF8».Находим нашу функцию sampAddChatMessage()
Что это значит? Дело в том что нельзя вызывать функции для работы с SA:MP пока сам SA:MP не готов. Что делать? Добавим перед выводом сообщения такой код:
if not isSampfuncsLoaded() or not isSampLoaded() then -- Если SF или SA:MP не загружены
return -- Завершаем работу скрипта
end
while not isSampAvailable() do -- Ждём пока функция isSampAvailable() вернет true
wait(0) -- Устанавливаем минимальное ожидание, что бы наша игра не зависла
-- значение 0 говорит что мы ждём следующий кадр (Frame)
end
Теперь наш скрипт будет ждать пока самп полностью не загрузится и продолжит выполнение кода.Что такое wait() и с чем его едят?
Wait позволяет ставить поток в режим ожидания на указанное количество мили-секунд (ms)
Wait нельзя в CallBack-функциях, это приведет к ошибке, а в последствии и смерти, скрипта.
Почему мы ставим 0? Потому-что это минимальная задержка.
Подведем краткий итог кода:
function main()
if not isSampfuncsLoaded() or not isSampLoaded() then
return
end
while not isSampAvailable() do
wait(0)
end
sampAddChatMessage("Привет мир!", 0xFFFFFFFF)
wait(-1)
end
Теперь при входе в игру / перезапуске скрипта в чат будет выводиться сообщение «Привет мир».К списку вопросов
Взаимодействие с игроками
Перейдем к примерам посложнее — попробуем получить ник, ид и счет игрока в которого мы прицелились.
Цель поставили, какие нам нужны средства?
Опять бежим к WIKI, всё в тот же раздел функций и ищем функции связанные с целями, Поиск -> Target.
Смотрим в списке есть getCharPlayerIsTargeting(ped).
Она возвращает 2 значения — result, ped.
Первое значение — возвращает результат проверки на целится ли указанный Ped в кого-либо.
Второе — если указанный Ped целится — возвращает саму цель.
Как нам это использовать? Всё просто, уберем бесконечное ожидание и будем проверять значение result.
function main()
if not isSampfuncsLoaded() or not isSampLoaded() then
return
end
while not isSampAvailable() do
wait(0)
end
sampAddChatMessage("Привет мир!", 0xFFFFFFFF)
while true do -- Создаем бесконечный цикл, вместо бесконечного ожидания
wait(0) -- Опять таки чтобы наша игра не зависла, ждем след кадр
local result, ped = getCharPlayerIsTargeting(PLAYER_HANDLE) -- Каждый кадр получаем данные функции, PLAYER_HANDLE - возвращает ваш Handle
if result then -- Если result вернет true
print("True!") -- Выведем сообщение в лог / консоль
end
end
end
Что мы можем с этим делать? Теперь если мы целимся в кого-либо, мы увидим сообщения в moonloader.log об этом. Мы получили Ped игрока в которого мы целимся. Давайте используем эту информацию.function main()
if not isSampfuncsLoaded() or not isSampLoaded() then
return
end
while not isSampAvailable() do
wait(0)
end
sampAddChatMessage("Привет мир!", 0xFFFFFFFF)
while true do
wait(0)
local result, ped = getCharPlayerIsTargeting(playerPed)
if result then
local result2, id = sampGetPlayerIdByCharHandle(ped) -- Попробуем получить ID игрока по его Ped
if result2 then -- Если получилось
local nickname = sampGetPlayerNickname(id) -- Запишем его ник
local score = sampGetPlayerScore(id) -- и счет
sampAddChatMessage(string.format("%s[%d] имеет счет %d", nickname, id, score), -1) -- И выведем это в чат
end
end
end
end
К списку вопросов
Работа с переменными
Что у нас получилось? Мы будем получать кучу сообщений в чат о игрока по пока целимся в кого-либо, но как убрать этот флуд?
Давайте создадим переменную перед main(), например targeting, и присвоим ей значение false.
И задействуем её в коде
local targeting = false -- Создаем булевую переменную
function main()
if not isSampfuncsLoaded() or not isSampLoaded() then
return
end
while not isSampAvailable() do
wait(0)
end
sampAddChatMessage("Привет мир!", 0xFFFFFFFF)
while true do
wait(0)
local result, ped = getCharPlayerIsTargeting(playerPed)
if result and not targeting then -- Если мы целимся, но ранее ни в кого не целились
local result2, id = sampGetPlayerIdByCharHandle(ped)
if result2 then
local nickname = sampGetPlayerNickname(id)
local score = sampGetPlayerScore(id)
sampAddChatMessage(string.format("%s[%d] имеет счет %d", nickname, id, score), -1)
targeting = true -- запишем что мы целимся в кого либо и наше условие выше станет ложным
end
elseif not result and targeting then -- Если условие обратное - скажем скрипту что мы не целимся
targeting = false
end
end
end
Отлично, теперь если мы целимся в игрока — получаем одно сообщение, меняем цель или заново целимся в того же игрока — снова получим о нём информацию.К списку вопросов
Работа с командами.
Уже лучше, давайте теперь работаем с командами.
Уберем лишний код и оставим следующее:
function main()
if not isSampfuncsLoaded() or not isSampLoaded() then
return
end
while not isSampAvailable() do
wait(0)
end
while true do
wait(0)
end
end
Как добавить команду? Давайте опять порыщем на WIKI, Поиск -> CommandНашли sampRegisterChatCommand(command, callback)? Отлично, можно уже приступать.
Зарегистрируем нашу команду, например «toster»:
function main()
if not isSampfuncsLoaded() or not isSampLoaded() then
return
end
while not isSampAvailable() do
wait(0)
end
sampRegisterChatCommand("toster", tosterCallBack) -- Регистрируем ДО бесконечного цикла.
-- Первый параметр - команда, без слэша.
-- Второй - функция-callback которая будет вызваться если будет введена команда.
wait(-1) -- Уберем бесконечный цикл, он тут вовсе не нужен, но нам же нужно чтобы скрипт работал
end
function tosterCallBack(params)
sampAddChatMessage("Дзынь!", -1)
end
Теперь при вводе команды «/toster» мы увидим в чате сообщение. Успех? Но как например вывести свой текст в чат? Легко!sampAddChatMessage(params, -1)
Теперь мы выводим весь текст после команды «/toster» в чат. Например: /toster Привет жалкий мирок!Помните я говорил что нельзя использовать wait() внутри callback-функций, это всё еще так, но есть тут одна хитрость!
function tosterCallBack()
sampAddChatMessage("Дзынь!", -1)
lua_thread.create(function() -- Создаем новый поток
wait(5000) -- Ждём 5 секунд
sampAddChatMessage("Тоже дзынь, но на 5 секунд позже!", -1) -- Выводим текст в чат
end) -- Тут наш поток умирает :(
end
К списку вопросов На сегодня всё, теперь вы чертовы волшебники, поняв как всё работает, можно приступать к изучению примеров Lua скриптов!
Тема будет дополняться по мере изучения популярных вопросов по Lua скриптингу.
Спойлер: ХейтерамЯ понимаю что вы невъебенные мастера луа скриптинга, но это тема не для вас
Lua в квик: набор для начинающего
Язык луа предоставляет достаточно широкие возможности для создания инфраструктуры торговых алгоритмов. Большой плюс Луа его интегрированность непосредственно в торговый терминал квик, что снижает риск обрыва потока данных из квика в стороннюю оболочку, снижение времени на передачу данных, его обработку и передачу торговых сигналов обратно в квик. Кроме этого, луа позволяет создать собственный интерфейс внутри квика, отслеживать исполнение (полное и частичное) ордеров, возможность встраивать элементы ММ.Документация по Луа, как им то невооборазимым образом разбросана по разным документам. Разумеется пользоваться скриптами будет небольшое кол-во пользователей, тем не менее, документацию по языку все же можно было собрать в одном месте.
Итак, синтаксис языка и его описание
Если с синтаксисом языка все понятно, двигаем дальше.
Проверим, готов ли наш квик к обработке скриптов на луа.
Работу QLua обеспечивает файл qlua.dll, который должен находиться в одной папке с файлами рабочего места QUIK, например, C:\Program Files\QUIK. Если файла нет, пробуем обновить квик или скачиваем dll с сайта quik.ru
Программирование
Скрипт пишется непосредственно в текстовом файле и сохраняется с расширением .lua
Возможно 3 подхода к написанию скриптов:
1. Скрипт пишется в текстовом файле и при его запуске выполняется 1 раз. Удобен для выполнения каких то разовых операций.
2. Можно написать в Lua-скрипте функцию с предопределенным именем и всю логику работы робота (или вычислительного скрипта) поместить в эту функцию. Функция main выполняется в отдельном потоке, т.е. она не мешает работе основного функционала терминала QUIK, наличие функции позволяет выполнять периодически приостанавливать скрипт и возобновлять его работу. Если зациклить main() и вставить sleep(), то получаем полную эмуляцию подхода, использующегося при программировании на встроенном QPILE: периодический расчет чего-либо через заданный интервал времени.
3. В QLUA доступна событийная модель программирования.
При выборе такого подхода получаем весьма гибкую среду выполнения пользовательских скриптов внутри QUIK, позволяющую мгновенно получать интересующие события от терминала, производя нужную нам обработку этих событий. Для того, чтобы обработать то или иное событие, необходимо просто прописать в своем скрипте функцию с предопределенным названием. Все доступные функции обработки событий есть в документации по QLUA. Поддерживаются самые различные события совершение на бирже очередной сделки, выставление новой заявки пользователем (или скриптом), изменение стакана котировок и т.д. Схематически принцип выполнения скриптов внутри терминала можно изобразить следующим образом:
Скрипт LUA в QUIK может содержать несколько функций с предопределенными названиями, являющимися обработчиками событий (таких как новая сделка, изменение лимитов, изменение котировок и т.д.).
Выполнение скрипта происходит после пожатия на кнопку «Запустить» в диалоге «Таблицы -> Lua -> Доступные скрипты», и всегда начинается с обработки тела скрипта вне каких-либо функций (на схеме обозначено [BODY]) и вызова обработчика с именем Init() (если он есть). После того, как функция Init() завершится, происходит создание отдельного потока приложения QUIK, и в этом потоке начинает выполняться функция main(), которая обязательно должна быть. Скрипт считается работающим до тех пор, пока выполняется функция main. Как только она завершится — прекращается и выполнение скрипта, т.е. вызов из него обработчиков событий. Обратите внимание, что все функции обработки событий, в отличие от функции main(), выполняются в рамках основного потока терминала QUIK, а значит время их работы должно быть сравнительно небольшим, иначе будут заметны «подвисания» в работе терминала.
Начать программирование можно с такого кода:
is_run = true
function main()
while is_run do
sleep(1000) — приостановка (1000 = на 1 секунду)
robot()
end
end
function robot() —тело робота
— здесь ваш код
end —конец тела робота
function OnStop(stop_flag)
is_run=false
stop_flag=1
end
Описание функций находятся здесь в файле «Интерпетатор языка луа» или в веб-версии здесь.
К сожалению, описание функций не полное и когда вы дойдете то программирования отправки транзакций (функция «sendtransaction()» ) то обнаружите что параметры нужно искать в другом месте, а именно в «Руководстве пользователя квик» в разделе 6 «Работа с другми приложениями», правда удобно?)) Я потратил несколько минут на поиски.
В остальном язык сильно проще и приятнее QPILE.
Примеры работающего робота, торгующего в спреде здесь. Его принцип работы и описание в предыдущем посте.
текст частично взят отсюда
База скрипта в QLua (lua)
Функция main выполняется в отдельном потоке, в этом весь смысл, чтобы его понять лучше, запустите вот такой скрипт:1 2 3 | while true do sleep(10000) end |
Попробуйте что-то сделать в терминале после запуска такого скрипта. Ваш терминал будет «висеть» 10 секунд, потому что цикл while будет выполняться в основном потоке терминала, в том же где происходит все взаимодействие с пользователем, т.е. с Вами.
Потом запустите вот такой скрипт:
1 2 3 4 5 | function main() while true do sleep(10000) end end |
Здесь тот же самый цикл, только уже внутри main, после запуска такого скрипта терминал будет работать как обычно, как будто никакого скрипта и не выполняется, потому что для функции main терминал создаст отдельный поток, параллельный основному. Чтобы понять что такое потоки, можете сравнить их с рабочими на фабрике, один рабочий — один поток. Представьте, что у Вас есть один рабочий, который занимается отрисовкой всего в терминале, он все отрисовывает, все работает, все хорошо. И тут Вы ему говорите: «Выполни мой скрипт», он говорит: «Хорошо», бросает отрисовку, начинает выполнять Ваш скрипт и видит там команду sleep(10000), для него это означает «ничего не делай 10 секунд», он говорит: «хорошо», и просто сидит на стуле 10 секунд 🙂 Естественно никто в это время ничего в терминале не перерисовывает и не реагирует на действия пользователя. Посидел работник 10 секунд и дальше продолжил заниматься отрисовкой, терминал «ожил». А вот если Вы ему «скажите» выполнить второй скрипт с main, то он, увидев main, подзывает другого работника, который ничего не делал и перекладывает эту задачу на него, а сам продолжает заниматься отрисовкой, т.к. наш первый работник у них главный, он может другим задачи распределять 🙂 Второй работник начинает выполнять все, что находится в функции main и никак не отвлекает «бригадира» от его работы.
Сразу могу провести аналогию с функциями обратного вызова. Если, например, мы запустили такой скрипт:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | run = true function main() while run do sleep(10) end message('Пока') end function OnTrade() message('Появилась новая сделка') end function OnStop() run = false end |
«Бригадир» смотрит что в нем сверху вниз, видит run = true, записывает себе в журнал, что в таком-то скрипте есть переменная run и она равна true, смотрит дальше, есть функция main, значит нужно будет привлечь другого работника, смотрит дальше, и видит функцию обратного вызова OnTrade, а в должностной инструкции бригадира написано, что это его обязанность обрабатывать функции обратного вызова, он у себя помечает в журнале, что когда придет информация о сделке пользователя, он должен выполнить эту функцию в этом скрипте, смотрит дальше, и видит что есть, так же, функция обратного вызова OnStop, опять помечает у себя в журнале, что если пользователь нажмет кнопку «Остановить» выделив перед этим данный скрипт в окне «Доступные скрипты», то он должен выполнить эту функцию . После этого он, как обычно, подзывает работника, говорит ему выполнять main и продолжает заниматься своей работой по обслуживанию терминала. Работник начинает выполнять то, что в main, смотрит, а в ней цикл «ПОКА(while) переменная run равна true ДЕЛАТЬ(do) «, проверяет переменную run, а она действительно равна true, и начинает делать то, что внутри цикла написано, видит там паузу 10 миллисекунд, ничего не делает 10 мс, идет дальше, видит там end, значит конец цикла, но так как это цикл, то он возвращается обратно к while и снова проверяет значение переменной run, оно по прежнему true и он опять выполняет паузу, и будет продолжать так делать, пока(while) переменная run равна true.
А в это время в терминал приходит информация, что по выставленной пользователем ранее заявке совершилась сделка, бригадир, получив эту информацию, смотрит свой журнал и видит, что при таком событии он должен выполнить функцию OnTrade в нашем скрипте, что он и делает, видит, что он должен вывести сообщение с текстом «Появилась новая сделка», делает это и продолжает заниматься своей работой. Сделок может быть много и каждый раз он будет выводить сообщение.
Потом вдруг нам надоело получать эти сообщения о сделках и мы нажали кнопку «Остановить» для этого скрипта. Бригадир опять читает свой журнал и видит, что должен выполнить функцию OnStop прежде, чем завершить наш скрипт. Переходит к ее выполнению, смотрит, а там написано, что он должен переменной run присвоить значение false, что он и делает, а так же ставит себе таймер на 5 секунд, после срабатывания которого он должен работнику, который выполняет main дать приказ завершить все работы и исчезнуть куда-нибудь, если он сам не придет в течении этих 5 секунд и не скажет, что он все выполнил 🙂
А в это время работник, в очередной раз проверяя значение переменной run, обнаруживает, что она уже не равна больше true, а равна false, а значит не нужно больше выполнять цикл, он переходит к тому, что ниже цикла, видит, что должен вывести сообщение с текстом «Пока», делает это, смотрит дальше, а дальше конец функции main, а значит и конец его работы. Радостный он бежит к бригадиру, сообщает, что все сделал, бригадир отпускает его с работы домой, а у себя из журнала вычеркивает переменную run и функции OnTrade и OnStop, после чего продолжает заниматься своей работой, как будто этого скрипта никогда и не было 🙂
как перестать встраивать и начать жить / Habr
Lua: как перестать встраивать и начать жить
За Lua прочно закрепилась слава полуязыка — инструмента, который при случае можно встроить, чтобы заскриптовать приложение, написанное на компилируемом языке вроде С++. Тем не менее Lua является вполне самостоятельным языком, имеющим свой интерпретатор, возможность создания модулей, большое число библиотек, и при этом данный ЯП обладает минимальным размером среди аналогов. Проще говоря у нас есть все, чтобы создавать такие же приложения как на perl, python, и вообще любом другом распространенном языке программирования.
Я могу предложить вам следующие доводы в пользу Lua:
- — приложения будут легко переносимы между Windows и Linux (не факт что код будет работать без изменений, но портирование правда пройдет безболезненно, если не были использованы платформоспецифичные библиотеки)
- — малый оверхед создаваемых программ
- — высокая скорость работы и загрузки приложений
- — возможность оперативно «приклеить» к вашему приложению любую С-библиотеку — лучшего «клея» для библиотек вы не найдете
- — приятный минималистичный синтаксис языка, с возможностью реализации на нем современных парадигм программирования
- — программы на Lua очень легко развертывать
- — малое потребление памяти
Чтобы продемонстрировать возможности Lua, я покажу как с его использованием создать небольшую программу для построения графиков по точкам с возможностью сохранения графика в виде файла изображения.
В качестве графического тулкита будем использовать iup — кроссплатформенную библиотеку, изначально созданную с расчетом использования из Lua.
Установка Lua SDK
В рамках идеи использования Lua как самостоятельного ЯП, была создана сборка Lua for Windows, которая содержит себе библиотеки, необходимые в повседневных задачах, возникающих при программировании под указанную ОС: работы с БД, GUI, парсинг XML и т.д. Пусть вас не смущает, что версия Lua в сборке 5.1, а не 5.2 — особой разницы в нашем случае между ними нет.
Скачайте и установите сборку.
Краткое описание концепции iup
Я долго думал, как же расписать процесс создания программы, не вдаваясь подробно в устройство iup. И решил коротко описать его основные принципы:
- — iup.dialog является корневым элементом интерфейса программы — в этом контейнере размещаются все элементы
- — позиционирование элементов в контейнере производится при помощи layout-ов: задания правил размещения элемента в контейнере. Iup сам расположит и отрисует элемент согласно правилам. Основные контейнеры — фрейм, вертикальный сайзер, горизонтальный сайзер.
- — обработчики событий задаются в виде функций, прикрепленных к виджету
- — после создания диалога запускается цикл обработки событий
Если вы ранее писали для GUI при помощи Tk, WxWidgets или WinAPI, то все это покажется знакомым. Если нет, то программа довольно подробно покрыта комментариями.
Код программы
-- подключение библиотек iup
require("iuplua" )
require("iupluacontrols")
require("iuplua_pplot")
-- библиотека для работы с Canvas, чтобы сохранять график в файл
require("cdlua")
require("iupluacd")
require("string")
-- глобальные переменные для виджетов и настроек программы
-- максимальное число графиков
plots_number = 5
-- виджеты вкладок, где будут размещаться виджеы ввода данных для каждого графика
tabs = {}
-- контейнеры для виджетов
vboxes = {}
-- чекбоксы для выбора того, какие графики строить
checkboxes = {}
-- здесь храним виджеты с текстом данных о точках
coords = {}
-- виджеты подписи для каждого графика
legends = {}
-- виджеты обозначения осей координат
global_legend = {}
-- к величайшему стыду, в Lua нет стандартной функции split
function string:split(sep)
local sep, fields = sep or ":", {}
local pattern = string.format("([^%s]+)", sep)
self:gsub(pattern, function(c) fields[#fields+1] = c end)
return fields
end
-- функция рисует на плоттере график по указаным точкам
function draw_plot(pwidget, pnum, data)
x = data[1].value:split(",")
y = data[2].value:split(",")
if checkboxes[pnum].value == "OFF" then return end
if not (#x == #y) or #x == 0 then
iup.Message("Ошибка",
"Задано неверное число точек для графика " .. pnum)
return
end
iup.PPlotBegin(pwidget, 0)
iup.PPlotAdd(pwidget, 0, 0)
for i = 1,#x do
iup.PPlotAdd(pwidget, x[i], y[i])
end
iup.PPlotEnd(pwidget)
end
-- виджет отвечающий за кнопку построения графика
plot_btn = iup.button{ title = "Построить"}
-- колбэк для кнопки "построить график"
function plot_btn:action()
-- создать виджет графопостроителя
plot = iup.pplot
{
expand="YES",
TITLE = "Simple Line",
MARGINBOTTOM="65",
MARGINLEFT="65",
AXS_XLABEL = global_legend[1].value,
AXS_YLABEL = global_legend[2].value,
LEGENDSHOW="YES",
LEGENDPOS="TOPLEFT",
size = "400x300"
}
-- этот блок для обхода бага - без него подпись к первому графику отображаться не будет
iup.PPlotBegin(plot, 0)
iup.PPlotAdd(plot,0,0)
plot.DS_LEGEND = ""
iup.PPlotEnd(plot)
-- обходим виджеты с данными
for i = 1, plots_number do
-- чтобы свеженарисованный графи отобразился с правильной подписью
print(legends[i].value)
plot.DS_LEGEND = legends[i].value
-- рисуем график
draw_plot(plot, i, coords[i])
end
-- кнопка сохранения графика в картинку на диске
save_btn = iup.button{ title = "Сохранить" }
-- теперь создаем само окно, где будет отображаться график
plot_dg = iup.dialog
{
iup.vbox -- это вертикальный сайзер, помести в него графопостроитель и кнопку
{
plot,
save_btn
},
}
-- обработчик кнопки сохранения графика
function save_btn:action()
-- создаем диалог выбора имени файла ля сохранения
-- в связи с ограничениями библиотеки сохранять можно только в EMF
fs_dlg = iup.filedlg{DIALOGTYPE = "SAVE", FILTER = "*.emf" }
iup.Popup(fs_dlg)
-- если файл выбран
if tonumber(fs_dlg.STATUS) >= 0 then
-- дописать при необходимости нужное расширение
pic = fs_dlg.value
if not (string.sub(pic, string.len(pic)-3) == ".emf") then
pic = pic .. ".emf"
end
-- создаем псевдо-холст, ассоциированный с файлом
tmp_cv = cd.CreateCanvas(cd.EMF, pic .. " 400x300")
-- выводим график на холст
iup.PPlotPaintTo(plot, tmp_cv)
-- сохраняем данные в файл
cd.KillCanvas(tmp_cv)
end
end
-- отображаем диалог с графиком
plot_dg:showxy(iup.CENTER, iup.CENTER)
-- запускаем петлю обработки событий для диалога
if (iup.MainLoopLevel()==0) then
iup.MainLoop()
end
end
-- в цикле создаем вкладки, в которых мы будем размещать виджеты
-- для сбора данных
for i=1,plots_number do
-- создание текстовых виджетов, куда будут вводиться координаты точек
coords[i] = {}
for j = 1,2 do
coords[i][j] = iup.text
{
expand="HORIZONTAL",
multiline = "YES",
VISIBLELINES = 5
}
end
-- виджет для редактирования подписи к графику
legends[i] = iup.text{ expand = "HORIZONTAL" }
-- создаем контейнер вкладки и заполняем его элементами
vboxes[i] = iup.vbox
{
iup.hbox
{
iup.label { title = "Подпись графика:" },
legends[i]
},
iup.hbox
{
iup.label
{
title="X : ",
},
coords[i][1]
},
iup.hbox
{
iup.label
{
title="Y : ",
},
coords[i][2]
};
expand="YES",
}
-- меняем заголовк вкладки
vboxes[i].tabtitle = "График " .. i
-- создаем чекбокс, который будет указывать на то, нужно ли строить
-- график по данным из указанной вкладки
checkboxes[i] = iup.toggle{ title= "График" .. i, value = "ON" }
end
-- теперь из заполненных нами контейнеров создаем вкладки
tabs = iup.tabs{unpack(vboxes)}
-- создаем текстовые виджеты для редактирования подписей осей
global_legend[1] = iup.text{}
global_legend[2] = iup.text{}
-- создаем фрейм для общих настроек графика
frame = iup.frame
{
iup.vbox
{
iup.label{
title="Использовать данные:",
expand="HORIZONTAL"
},
iup.vbox
{
unpack(checkboxes)
},
iup.label{}, -- пустую подпись можно использовать как распорку
iup.label{title = "Подписи"},
iup.hbox { iup.label{ title = "Ось X "}, global_legend[1] },
iup.hbox { iup.label{ title = "Ось Y "}, global_legend[2] },
iup.label{},
plot_btn
};
expand = "VERTICAL",
}
-- создаем главное окно программы и наносим на него настройки и табы
dg = iup.dialog
{
iup.hbox
{
frame, tabs
},
title="Строим график",
size = "HALF"
}
-- показываем главное окно и запускаем обработку событий
dg:showxy(iup.CENTER, iup.CENTER)
if (iup.MainLoopLevel()==0) then
iup.MainLoop()
end
Пара слов о развертывании
Скрипт можно запустить при помощи команды:
lua plotter.exe
В данном случае библиотеки будут подключаться из поддиректории clibs/, которая находится в директории, куда был установлен Lua for Windows. Чтобы максимально компактно упаковать скрипт и библиотеки для переноса на другую машину, достаточно скопировать в одну папку следущие файлы(указаны с относительными путями от директории установки Lua):
lua.exe
lib/lua5.1.dll
clibs/cd.dll
clibs/cdlua51.dll
clibs/iup.dll
clibs/iup_pplot.dll
clibs/iupcd.dll
clibs/iupcontrols.dll
clibs/iupgl.dll
clibs/iuplua51.dll
clibs/iuplua_pplot51.dll
clibs/iupluacd51.dll
clibs/iupluacontrols51.dll
clibs/freetype6.dll
Не забудьте поместить в эту папку и сам скрипт с программой. Теперь вы можете перенести эту папку на другую машину и запустить вашу программы командой, указанной выше. При этом никакие другие действия по установке библиотек и рантайма не нужны.
К сожалению файлы cd.dll, cdluad51.dll и iupcd.dll в данной версии Lua for Windows могут работать некорректно, поэтому рекомендую взять их из архива по ссылке ниже.
Итоги
Архив с рабочей версией тут, для удобства добавлена пускалка app.bat.
Скриншоты:
В результате получили, пусть и неказистую, утилиту, имеющую такой же функционал, как и если бы она была написана на «серьезном» языке программирования. При этом простую в развертывании и суммарным весом менее 2 мб. Потребление памяти — около 7 мб. Исходный код доступен для редактирования, сам Lua интерактивно понятен, что упрощает доработку подобного софта на местах.
На мой взгляд, это отличный выбор для написания учебного софта для школ и институтов, а также для внутреннего использования на предприятиях. Так как слабые машины до сих пор в изобилии присутствуют в подобных местах по всему СНГ, то использование Luа подобным образом целесообразно, особенно в свете постепнного прихода Linux на десктопы. К тому же тенденцию потери исходников самописного софта при жуткой его же забагованности можно приравнять к национальному бедствию.