проанализируй код на языке qlua, дай рекомендации по улучшению кода, может быть нужно его построить с одной глобальной таблицей в которую нужно записывать сложное разветвлённое дерево и данные для функций брать от туда, в общем предложи лучшие варианты.
---@diagnostic disable: undefined-field
local sleep = _G.sleep
local message = _G.message
local getParamEx = _G.getParamEx
local getCandles = _G.getCandles
local getWorkingFolder = _G.getWorkingFolder
local getScriptPath = _G.getScriptPath
local getBuySellInfo = _G.getBuySellInfo
---@diagnostic enable: undefined-field
local os = _G.os
local tonumber = _G.tonumber
local tostring = _G.tostring
local table = _G.table
local string = _G.string
local pairs = _G.pairs
local ipairs = _G.ipairs
local path = getWorkingFolder()
package.path = path .. '\\Script\\Libs\\?.lua;' .. package.path
local log = require("log")
local TICKER_FILE = "TICKER_1.TXT"
local DATA_FILE = "DATA.TXT"
local TIME_FRAMES = { "M1" } -- Пример таймфреймов
local EMA_PERIODS = { 3, 5} -- Периоды EMA
local TP_PERCENT = 1.0 -- Take Profit 1%
local SL_PERCENT = 0.5 -- Stop Loss 0.5%
local MAX_BARS = 13 -- Максимальное количество баров для хранения
local MODE = "SIMULATION" -- "HISTORY", "SIMULATION", "REAL"
local TRADING_CAPITAL = 1000000 -- Торговый капитал
local LOT_SIZE = 10 -- Количество лотов
local TRADE_INTERVALS = {
{ start = "12:01", close_time = "20:39" }, -- Утренняя сессия
{ start = "21:06", close_time = "01:49" } -- Вечерняя сессия
}
local market_status_history = {} -- Хранение последних трех статусов рынка
local data = {} -- Данные по тикерам
local log_file = nil -- Лог-файл
local transaction_queue = {} -- Очередь транзакций
local isRun = true -- Флаг работы скрипта
local os_time = os.time() -- Текущее время в формате Unix
local function update_bars(ticker, time_frame, new_price, current_time)
if not data[ticker].bars[time_frame] then
log.debug("Ошибка: Нет данных для таймфрейма " .. time_frame .. " у тикера " .. ticker)
return
end
local bars = data[ticker].bars[time_frame]
local last_bar_time = data[ticker].last_bar_time[time_frame]
local tick_data = data[ticker].tick_data -- Используем tick_data из структуры data
local interval = 60 -- M1 (60 секунд)
if time_frame == "M1" then
interval = 20
elseif time_frame == "M3" then
interval = 20
elseif time_frame == "M10" then
interval = 600
elseif time_frame == "M15" then
interval = 900
elseif time_frame == "M20" then
interval = 1200
end
if last_bar_time == 0 then
last_bar_time = current_time - interval -- Устанавливаем время начала первого бара
data[ticker].last_bar_time[time_frame] = last_bar_time
end
tick_data.sum_NP = tick_data.sum_NP + new_price
tick_data.sum_N = tick_data.sum_N + 1
log.debug(string.format("Тик для %s (%s): время = %s, цена = %.2f, сумма = %.2f, количество = %d",ticker, time_frame, os.date("%Y-%m-%d %H:%M:%S", current_time), new_price, tick_data.sum_NP, tick_data.sum_N))
if current_time - last_bar_time >= interval then
local avg_close = tick_data.sum_NP / tick_data.sum_N
log.debug(string.format("Формирование бара для %s (%s): время = %s, средняя цена = %.2f (сумма = %.2f, количество = %d)",ticker, time_frame, os.date("%Y-%m-%d %H:%M:%S", current_time), avg_close, tick_data.sum_NP, tick_data.sum_N))
table.insert(bars, {
time = current_time,
close = avg_close -- Цена закрытия = средняя цена тиков
})
data[ticker].last_bar_time[time_frame] = current_time
log.debug(string.format("Новый бар для %s (%s): время = %s, цена закрытия = %.2f",ticker, time_frame, os.date("%Y-%m-%d %H:%M:%S", current_time), avg_close))
tick_data.sum_NP = 0
tick_data.sum_N = 0
while #bars > MAX_BARS do
local removed_bar = bars[1]
table.remove(bars, 1)
log.debug(string.format("Удален старый бар для %s (%s): время = %s, цена закрытия = %.2f",ticker, time_frame, os.date("%Y-%m-%d %H:%M:%S", removed_bar.time), removed_bar.close))
end
end
end
local function calculate_ema(ticker, tf, period)
local bars = data[ticker].bars[tf]
if #bars < period then
log.debug("Недостаточно баров для расчета EMA. Требуется: " .. period .. ", доступно: " .. #bars)
return
end
local recent_bars = {}
for i = #bars - period + 1, #bars do
table.insert(recent_bars, bars[i])
end
local k = 2 / (period + 1)
local ema = recent_bars[1].close -- Первое значение EMA равно цене закрытия первого бара
for i = 2, #recent_bars do
ema = recent_bars[i].close * k + ema * (1 - k)
end
data[ticker].emas[tf][period] = ema
log.debug(string.format("Рассчитано EMA для %s (%s, период %d): %.4f", ticker, tf, period, ema))
end
local function add_transaction(ticker, operation, price, quantity)
table.insert(transaction_queue, {
ticker = ticker,
operation = operation,
price = price,
quantity = quantity
})
end
local function get_best_prices(ticker)
local best_bid = tonumber(getParamEx("TQBR", ticker, "BID").param_value) or 0
local best_ask = tonumber(getParamEx("TQBR", ticker, "OFFER").param_value) or 0
log.debug("best_bid " .. best_bid)
log.debug("best_ask " .. best_ask)
return best_bid, best_ask
end
local function send_order(ticker, operation, quantity)
local best_bid, best_ask = get_best_prices(ticker)
local last_price = tonumber(getParamEx("TQBR", ticker, "LAST").param_value) or 0 -- Получаем LAST
local price = 0
if operation == "BUY" then
price = best_ask -- Покупаем по лучшей цене продажи
elseif operation == "SELL" then
price = best_bid -- Продаем по лучшей цене покупки
elseif operation == "CLOSE" then
local current_position = data[ticker].positions
if current_position then
if current_position.signal == "BUY" then
price = best_bid -- Закрываем позицию BUY по лучшей цене покупки
elseif current_position.signal == "SELL" then
price = best_ask -- Закрываем позицию SELL по лучшей цене продажи
end
else
log.debug("Ошибка: Нет активной позиции для закрытия.")
return
end
end
if not price or price <= 0 then
price = last_price
log.debug(string.format("Цена для заявки %s не определена. Используется LAST: %.2f", operation, price))
end
if not price or price <= 0 then
log.debug("Ошибка: Невозможно определить цену для заявки " .. operation)
return
end
add_transaction(ticker, operation, price, quantity)
log.debug(string.format(
"Заявка %s для %s: цена = %.2f, количество = %d",
operation, ticker, price, quantity
))
end
local function is_short_enabled(ticker)
local class_code = "TQBR"
local sec_code = ticker
local firm_id = "MC0003300000" -- Замените на ваш firm_id
local client_code = "1360DK" -- Замените на ваш client_code
local price = 0 -- Цена для расчета лимитов (0 для текущей цены)
local buy_sell_info = getBuySellInfo(firm_id, client_code, class_code, sec_code, price)
if not buy_sell_info then
log.debug(string.format("Ошибка: Не удалось получить данные для %s через getBuySellInfo", ticker))
return false
end
local can_sell = tonumber(buy_sell_info.can_sell) or 0
return can_sell > 0
end
local function check_ema_cross(ticker, time_frame)
local ema_short = data[ticker].emas[time_frame][EMA_PERIODS[1]]
local ema_long = data[ticker].emas[time_frame][EMA_PERIODS[2]]
if not ema_short or not ema_long then
return "NONE"
end
local current_dir = (ema_short > ema_long) and "BUY" or "SELL"
local last_dir = data[ticker].last_dir or "NONE"
local last_market_status = market_status_history[#market_status_history]
local new_status = (ema_short > ema_long) and "LONG" or "SHORT"
if not last_market_status then
table.insert(market_status_history, new_status)
log.debug(string.format("Первое определение статуса рынка для %s: %s", ticker, new_status))
return "NONE"
end
local is_trade_allowed = false
if last_market_status == "LONG" and current_dir == "SELL" then
is_trade_allowed = true
elseif last_market_status == "SHORT" and current_dir == "BUY" then
is_trade_allowed = true
end
if is_trade_allowed and current_dir ~= last_dir then
data[ticker].last_dir = current_dir
log.debug(string.format("Пересечение средних для %s: %s -> %s (EMA short: %.4f, EMA long: %.4f)",
ticker, last_dir, current_dir, ema_short, ema_long))
local current_position = data[ticker].positions
if not current_position then
if current_dir == "SELL" and not is_short_enabled(ticker) then
log.debug(string.format("Шорт недоступен для %s. Пропускаем сигнал на продажу.", ticker))
return "NONE"
end
send_order(ticker, current_dir, LOT_SIZE)
else
send_order(ticker, "CLOSE", current_position.quantity)
send_order(ticker, current_dir, LOT_SIZE)
end
if #market_status_history > 0 then
market_status_history[1] = new_status -- Обновляем последний статус
else
table.insert(market_status_history, new_status) -- Добавляем первый статус
end
log.debug(string.format("Статус рынка для %s обновлен: %s", ticker, new_status))
else
log.debug(string.format("Статус рынка %s для %s. Сигнал %s игнорируется.",
last_market_status, ticker, current_dir))
end
return "NONE"
end
local function check_pending_orders()
for ticker, tdata in pairs(data) do
if tdata.pending_orders then
for i, order in ipairs(tdata.pending_orders) do
if os.time() - order.time > 60 then -- Если заявка висит более 60 секунд
log.debug(string.format("Снимаем заявку для %s: %s, цена = %.2f, количество = %d",
ticker, order.operation, order.price, order.quantity))
table.remove(tdata.pending_orders, i)
end
end
end
end
end
local function get_lot_size(ticker)
local lot = tonumber(getParamEx("TQBR", ticker, "LOTSIZE").param_value)
log.debug("--lot: " .. tostring(lot))
if not lot or lot <= 0 then
log.debug("Ошибка: Не удалось получить реальный размер лота для " .. ticker)
lot = 1
end
return lot
end
local function manage_tp_sl(ticker)
local pos = data[ticker].positions
if pos then
local best_bid, best_ask = get_best_prices(ticker)
local current_price = pos.signal == "BUY" and best_bid or best_ask
local open_price = pos.price -- Цена открытия позиции
local profit_percent = (current_price - open_price) / open_price * 100
local loss_percent = (open_price - current_price) / open_price * 100
if profit_percent >= TP_PERCENT or loss_percent >= SL_PERCENT then
log.debug(string.format(
"%s сработал для %s: прибыль/убыток = %.2f%%",
profit_percent >= TP_PERCENT and "Take Profit" or "Stop Loss",
ticker, profit_percent >= TP_PERCENT and profit_percent or loss_percent
))
send_order(ticker, "CLOSE", pos.quantity) -- Закрываем позицию
local signal = check_ema_cross(ticker, TIME_FRAMES[1])
if signal ~= "NONE" then
if data[ticker].market_status == "LONG" and signal == "SELL" then
send_order(ticker, "SELL", LOT_SIZE)
elseif data[ticker].market_status == "SHORT" and signal == "BUY" then
send_order(ticker, "BUY", LOT_SIZE)
end
end
end
end
end
local function get_current_positions()
local positions = {}
for ticker, tdata in pairs(data) do
if tdata.positions then
positions[ticker] = tdata.positions.quantity
end
end
return positions
end
local function process_transactions()
while #transaction_queue > 0 do
local tr = table.remove(transaction_queue, 1)
local ticker = tr.ticker
local operation = tr.operation
local price = tr.price
local quantity = tr.quantity
if MODE == "REAL" then
log.debug("Реальный режим: " .. operation .. " " .. ticker .. ", лотов: " .. quantity)
elseif MODE == "SIMULATION" then
log.debug("Симуляция: " .. operation .. " " .. ticker .. ", лотов: " .. quantity)
message("Симуляция: " .. operation .. " " .. ticker .. ", лотов: " .. quantity)
else
log.debug("История: " .. operation .. " " .. ticker .. ", лотов: " .. quantity)
end
local real_lot_size = get_lot_size(ticker)
if operation == "CLOSE" then
local pos = data[ticker].positions
if pos then
table.insert(data[ticker].trades, {
direction = pos.signal,
open_price = pos.price,
close_price= price,
quantity = pos.quantity,
profit = (price - pos.price) * pos.quantity * real_lot_size
})
TRADING_CAPITAL = TRADING_CAPITAL + price * pos.quantity * real_lot_size
data[ticker].positions = nil
end
else
TRADING_CAPITAL = TRADING_CAPITAL - price * quantity * real_lot_size
data[ticker].positions = {
price = price,
signal = operation,
quantity= quantity
}
end
end
end
local function calculate_profit()
local initial_capital = TRADING_CAPITAL
local total_profit = 0
log.debug("--- Результаты торговли ---")
for ticker, tdata in pairs(data) do
local ticker_profit = 0
if ticker_profit ~= 0 then
log.debug("Тикер: " .. ticker)
end
for i, trade in ipairs(tdata.trades) do
local lot_size = get_lot_size(ticker)
local profit
if trade.direction == "BUY" then
profit = (trade.close_price - trade.open_price) * trade.quantity * lot_size
elseif trade.direction == "SELL" then
profit = (trade.open_price - trade.close_price) * trade.quantity * lot_size
else
profit = 0
end
profit = tonumber(string.format("%.2f", profit))
ticker_profit = ticker_profit + profit
if profit ~= 0 then
log.debug(" Сделка #" .. i)
log.debug(" Направление: " .. trade.direction)
log.debug(" Цена открытия: " .. trade.open_price)
log.debug(" Цена закрытия: " .. trade.close_price)
log.debug(" Кол-во лотов: " .. trade.quantity)
log.debug(" Прибыль: " .. profit)
message("Тикер: " .. ticker .. " Прибыль: " .. profit)
end
end
local ticker_percent = (ticker_profit / initial_capital) * 100
total_profit = total_profit + ticker_profit
if ticker_profit ~= 0 then
log.debug(" Итог по " .. ticker)
log.debug(" Прибыль: " .. ticker_profit)
log.debug(" % от депозита: " .. string.format("%.2f", ticker_percent) .. "%")
message(" Итог по " .. ticker .. " Прибыль: " .. ticker_profit)
end
end
local total_percent = (total_profit / initial_capital) * 100
log.debug("--- Итог ---")
log.debug("Общая прибыль: " .. string.format("%.2f", total_profit))
log.debug("Общий процент прибыли: " .. string.format("%.2f", total_percent) .. "%")
message("Общая прибыль: " .. string.format("%.2f", total_profit))
end
local function close_all_positions()
local positions = get_current_positions()
for ticker, quantity in pairs(positions) do
local price = tonumber(getParamEx("TQBR", ticker, "LAST").param_value) or 0
add_transaction(ticker, "CLOSE", price, quantity)
log.debug("Закрытие позиции: " .. ticker .. ", количество: " .. quantity)
end
process_transactions() -- Обрабатываем все транзакции
end
local function read_tickers()
for line in io.lines(TICKER_FILE) do
local ticker = line:gsub("%s+", "")
data[ticker] = {
bars = {},
emas = {},
positions = nil,
trades = {},
tick_data = { sum_NP = 0, sum_N = 0 },
last_bar_time = {},
last_dir = "NONE",
}
for _, tf in ipairs(TIME_FRAMES) do
data[ticker].bars[tf] = {}
data[ticker].emas[tf] = {}
data[ticker].last_bar_time[tf] = 0
end
end
end
local function read_historical_data()
if MODE == "HISTORY" then
local file = io.open(DATA_FILE, "r")
if file then
for line in file:lines() do
local ticker, time_str, price_str = line:match("(%S+),(%S+),(%S+)")
local time_val = tonumber(time_str)
local price = tonumber(price_str)
if data[ticker] and time_val and price then
for _, tf in ipairs(TIME_FRAMES) do
table.insert(data[ticker].bars[tf], {
time = time_val,
close = price
})
end
end
end
file:close()
else
message("Ошибка: Не удалось открыть файл " .. DATA_FILE, 1)
end
else
message("Режим " .. MODE .. ": данные загружаются онлайн.", 1)
end
end
local function is_evening_session_allowed(ticker)
local ev_sess_result = getParamEx("TQBR", ticker, "EV_SESS_ALLOWED")
if not ev_sess_result or not ev_sess_result.param_image then
log.debug(string.format("Ошибка: Не удалось получить параметр EV_SESS_ALLOWED для %s.", ticker))
return false -- Запрещаем торговлю, если данные не получены
end
local ev_sess_allowed = tostring(ev_sess_result.param_image)
if ev_sess_allowed == "Да" then
log.debug(string.format("Вечерняя сессия доступна для %s.", ticker))
return true
else
log.debug(string.format("Вечерняя сессия недоступна для %s.", ticker))
return false
end
end
local function can_trade(ticker)
local current_time = os.date("*t", os_time)
local current_minutes = current_time.hour * 60 + current_time.min
log.debug(string.format("Текущее время: %02d:%02d", current_time.hour, current_time.min))
for _, interval in ipairs(TRADE_INTERVALS) do
local start_hour, start_min = interval.start:match("(%d+):(%d+)")
local end_hour, end_min = interval.close_time:match("(%d+):(%d+)")
local start_minutes = tonumber(start_hour) * 60 + tonumber(start_min)
local end_minutes = tonumber(end_hour) * 60 + tonumber(end_min)
if interval.start == "21:06" and interval.close_time == "01:49" then
if not data[ticker] then
log.debug(string.format("Ошибка: Данные для тикера %s не инициализированы.", ticker))
return false -- Запрещаем торговлю
end
if data[ticker].evening_session_allowed == nil then
log.debug(string.format("Ошибка: Поле evening_session_allowed для %s не инициализировано.", ticker))
return false -- Запрещаем торговлю
end
if not data[ticker].evening_session_allowed then
log.debug(string.format("Вечерняя сессия недоступна для %s. Интервал %s - %s пропущен.",ticker, interval.start, interval.close_time))
return false
end
end
if end_minutes < start_minutes then
if current_minutes >= start_minutes or current_minutes < end_minutes then
log.debug(string.format("Торговля разрешена в интервале: %s - %s",interval.start, interval.close_time))
return true
end
else
if current_minutes >= start_minutes and current_minutes < end_minutes then
log.debug(string.format("Торговля разрешена в интервале: %s - %s",interval.start, interval.close_time))
return true
end
end
end
log.debug("Торговля запрещена: текущее время вне торговых интервалов.")
return false
end
local tasks = {
{
name = "idle_check", -- Проверка бездействия
interval = 60, -- Интервал: 60 секунд
last_execution = os.time(),
callback = function()
log.debug("Проверка бездействия...")
end
},
{
name = "main_loop", -- Основной цикл
interval = 0.1, -- Интервал: 100 миллисекунд
last_execution = os.time(),
callback = function()
log.debug("Выполнение основного цикла...")
if can_trade() then -- <-- Используем can_trade
for ticker, tdata in pairs(data) do
local raw = getParamEx("TQBR", ticker, "LAST")
if raw and raw.param_value then
local price = tonumber(raw.param_value)
if price then
local current_time = os.time()
for _, tf in ipairs(TIME_FRAMES) do
update_bars(ticker, tf, price, current_time)
end
local signal = check_ema_cross(ticker, TIME_FRAMES[1])
if signal ~= "NONE" then
if tdata.positions then
add_transaction(ticker, "CLOSE", price, tdata.positions.quantity)-- Закрываем текущую позицию
add_transaction(ticker, signal, price, LOT_SIZE) -- Открываем новую позицию
else
add_transaction(ticker, signal, price, LOT_SIZE) -- Открываем новую позицию
end
end
manage_tp_sl(ticker)
process_transactions()
end
end
end
end
end
},
{
name = "last_update", -- Обновление LAST и вызов calculate_ema
interval = 1, -- Интервал: 1 секунда
last_execution = os.time(),
callback = function()
for ticker, _ in pairs(data) do
local last_price = tonumber(getParamEx("TQBR", ticker, "LAST").param_value)
if last_price then
log.debug("Обновление LAST для " .. ticker .. ": " .. last_price)
end
for _, tf in ipairs(TIME_FRAMES) do
for _, period in ipairs(EMA_PERIODS) do
calculate_ema(ticker, tf, period) -- Вызов функции
end
end
end
end
},
{
name = "lotsize_update", -- Обновление LOTSIZE и SHORT_ENABLED
interval = nil, -- Однократное выполнение
last_execution = nil,
callback = function()
for ticker, tdata in pairs(data) do
local lot_size = tonumber(getParamEx("TQBR", ticker, "LOTSIZE").param_value)
if lot_size then
tdata.lot_size = lot_size
log.debug(string.format("Размер лота для %s: %d",ticker, lot_size))
else
log.debug(string.format("Ошибка: Не удалось получить размер лота для %s",ticker))
tdata.lot_size = 1 -- Значение по умолчанию
end
data[ticker].evening_session_allowed = is_evening_session_allowed(ticker)
log.debug(string.format("Вечерняя сессия для %s: %s",ticker, data[ticker].evening_session_allowed and "доступна" or "недоступна"))
tdata.short_enabled = is_short_enabled(ticker)
log.debug(string.format("Шорт для %s: %s",ticker, tdata.short_enabled and "доступен" or "недоступен"))
end
end
},
{
name = "check_pending_orders", -- Проверка и снятие неисполненных заявок
interval = 10, -- Интервал: 10 секунд
last_execution = os.time(),
callback = function()
check_pending_orders()
end
}
}
local function execute_tasks()
local current_time = os.time()
for _, task in ipairs(tasks) do
if not task.last_execution then
task.callback()
task.last_execution = current_time
end
if task.interval and (current_time - task.last_execution >= task.interval) then
task.callback()
task.last_execution = current_time
end
end
end
function OnInit()
log.openfile(getScriptPath() .. "\\777.log", "w")
log.level = 'debug'
read_tickers()
read_historical_data()
log.debug("Робот запущен, режим: " .. MODE)
log.debug("Доступные средства: " .. TRADING_CAPITAL)
end
function OnStop()
log.debug("Запуск процедуры остановки робота...")
close_all_positions()
calculate_profit()
if log_file then
log_file:close()
log_file = nil
end
log.debug("Робот остановлен")
isRun = false
end
function _G.main()
while isRun do
execute_tasks()
sleep(10) -- Спим 10 миллисекунд
end
end