// INIZIO PARTE NON MODIFICABILE

class ES_NUOVO_7_TS_Class extends TSBaseClass
{
    var SystemContext;
    var UserContext;
    var SafeContext;
    var stock;
    var Par1;
    var Secondi;
    var ps;
    
    function ES_NUOVO_7_TS_Class()
    {
        this.stock = null;
        this.Par1 = 0;
        this.Secondi = 0;
        this.SystemContext = null;
        this.UserContext = null;
        this.SafeContext = null;
        this.ps = null;
    }
    
    function Init(stock, Par1, Secondi, ps, systemContext, safeContext)
    {
        this.SystemContext = systemContext;
        this.UserContext = new Object();
        this.SafeContext = safeContext;
        this.stock = stock;
        this.Par1 = Number(Par1);
        this.Secondi = Number(Secondi);
        this.ps = ps;
        this.UserContext.Clear = function () { for (var prop in this) { if (prop != 'Clear') { delete this[prop]; } } }
        this.SafeContext.Clear = function () { for (var prop in this) { if (prop != 'Clear') { delete this[prop]; } } }
    }
    
    function Evaluate()
    {
        // FINE PARTE NON MODIFICABILE
        // INIZIO PARTE MODIFICABILE CHE SI PUO' MODIFICARE FINO AL TERMINE
// 1. Configurazione
var CONFIG = {
    ORDER_SIDE: 'BID',
    SIZE: 1,
    MAX_OPERATIONS: 3,
    LEVELS: { BID: 6, ASK: 6 },
    SECONDS: 5,
    TICK: 1,
    MONITORING_MODE: 'MONITORA_NLevels',
    SIDES: ['BID', 'ASK'],
    TYPES: ['Price', 'Quantity']
};

// 2. ChangeType
var ChangeType = {
    ARRAY: 'array',
    PRICE: 'price'
};

// 3. LogManager
var LogManager = (function () {
    function MessageFormatter() {
        this.separator = "---------------------------------------------------\r\n";
        this.messages = {
            'Initialization Complete': "Inizializzazione completata:\nBid Price 0: {bidPrice}\nAsk Price 0: {askPrice}\n",
            'Order Action': 'Ordine {action}: {orderCode}\n',
            'Order Deleted': 'Ordine cancellato: {orderCode}\n',
            'LevelChange': "Livello da {lastLevel} all'attuale livello {priceLevel}\n",
            'PriceChange': "Prezzo da {previousPrice} all'attuale prezzo {currentPrice}\nOra: {currentTime}\r\n",
            'Operation Number': "Numero Operazione: {numOperazione}\r\n",
            'message': "{message}\r\n"
        };
    }

    MessageFormatter.prototype.format = function (template, data) {
        return template.replace(/{(\w+)}/g, function (match, key) {
            return data.hasOwnProperty(key) ? data[key] : match;
        });
    };

    function formatBookSide(bookSide, sideName, levels, separator) {
        var sideMessage = separator + sideName + ":\n";
        var pricesLength = bookSide.Prices.length;
        for (var level = 0; level < levels && pricesLength > level; level++) {
            sideMessage += "Level " + level + ": Price = " + bookSide.Prices[level] + ", Quantity = " + bookSide.Quantities[level] + "\n";
        }
        return sideMessage;
    }

    function formatBookState(book, prices, numOperazione, separator) {
        var formattedState = separator;
        formattedState += "Bid Price \tBid Quant.\tAsk Price \tAsk Quant.\r\n";
        formattedState += separator;
        formattedState += getSideData(book, 'BID', 0, 'Prices') + "\t\t" + getSideData(book, 'BID', 0, 'Quantities') + "\t\t";
        formattedState += prices.ASK.current + "\t\t" + prices.ASK.lastQuantity + "\r\n";
        formattedState += separator;
        formattedState += "Numero Operazione: " + numOperazione + "\r\n";
        formattedState += separator;
        return formattedState;
    }

    function Logger(formatter) {
        this.formatter = formatter;
    }

    Logger.prototype.log = function (type, data, includeFormattedState, book, prices, numOperazione) {
        if (!this.formatter.messages.hasOwnProperty(type)) return;
        var message = this.formatter.separator + this.formatter.format(this.formatter.messages[type], data || {});
        if (includeFormattedState !== false && (type === 'Initialization Complete' || type === 'LevelChange')) {
            message += formatBookState(book, prices, numOperazione, this.formatter.separator);
        }
        Log(message);
    };

    Logger.prototype.logFullBook = function (book, config) {
        var message = [
            formatBookSide(book.BID, 'BID', config.LEVELS.BID, this.formatter.separator),
            this.formatter.separator,
            formatBookSide(book.ASK, 'ASK', config.LEVELS.ASK, this.formatter.separator)
        ];
        Log(message.join(''));
    };

    Logger.prototype.initializeOrderAndLog = function (OrderProcessor, BookUtils, book, prices, config) {
        var initialPrice = BookUtils.getData(book, config.ORDER_SIDE, getTargetPriceLevel(config.ORDER_SIDE, config), 'Price');
        OrderProcessor.placeOrder(config.ORDER_SIDE, stock, initialPrice, book, prices, config);
        this.log('Initialization Complete', {
            bidPrice: prices.BID.current,
            askPrice: prices.ASK.current
        }, true, book, prices, UserContext.status.numOperazione);
        this.logFullBook(book, config);
    };

    Logger.prototype.logPriceChange = function (ctx) {
        this.log('PriceChange', {
            previousPrice: ctx.lastPrice,
            currentPrice: ctx.currentPrice,
            currentTime: Now().ToString("HH:mm:ss.ff")
        });
        ctx.lastPrice = ctx.currentPrice;
    };

    Logger.prototype.logTickCalculation = function (tick) {
        this.log('message', { message: 'Tick calcolato: ' + tick }, false);
    };

    var messageFormatter = new MessageFormatter();
    var logger = new Logger(messageFormatter);

    return {
        getLogger: function () {
            return logger;
        }
    };
})();

// 4. calculateTick
function calculateTick(bidPrices, askPrices, config, logger) {
    var MINIMUM_TICK = 0.005;
    if (!bidPrices || !askPrices || bidPrices.length < 2 || askPrices.length < 2) {
        logger.logTickCalculation('0');
        return 0;
    }
    var minDiff = Infinity;
    var len = Math.min(bidPrices.length, askPrices.length);
    for (var i = 0; i < len - 1; i++) {
        var bidDiff = Math.abs(bidPrices[i + 1] - bidPrices[i]);
        var askDiff = Math.abs(askPrices[i + 1] - askPrices[i]);
        if (bidDiff < minDiff && bidDiff >= MINIMUM_TICK) minDiff = bidDiff;
        if (askDiff < minDiff && askDiff >= MINIMUM_TICK) minDiff = askDiff;
        if (minDiff === MINIMUM_TICK) break;
    }
    if (minDiff === Infinity) {
        logger.logTickCalculation('0');
        return 0;
    }
    var tick = Math.round(minDiff * 1000) / 1000;
    logger.logTickCalculation(tick.toString());
    return tick;
}

// 5. StateManager
var StateManager = (function () {
    function checkStateChange(context, newState, changeType, type) {
        var hasChanged = hasStateChanged(context, newState, changeType);
        if (hasChanged) {
            context = updateContext(context, type, 'update', { data: newState });
        }
        return hasChanged;
    }

    return {
        checkStateChange: checkStateChange
    };
})();

// Funzioni di utility per la gestione del book
function getSideData(book, side, level, dataType) {
    if (typeof book === 'object' && book !== null && side in book && level >= 0) {
        var sideData = book[side];
        if (Object.prototype.toString.call(sideData[dataType]) === '[object Array]' && sideData[dataType].length > level) {
            return sideData[dataType][level];
        }
    }
    return undefined;
}

function extractPricesQuantities(source, levels, isFunc) {
    var prices, quantities;
    if (isFunc) {
        prices = new Array(levels);
        quantities = new Array(levels);
        for (var i = 0; i < levels; i++) {
            prices[i] = source.Price(i);
            quantities[i] = source.Quantity(i);
        }
    } else {
        prices = (source.Prices || []).slice(0, levels);
        quantities = (source.Quantities || []).slice(0, levels);
    }
    return { Prices: prices, Quantities: quantities };
}

function isArrayEqual(arr1, arr2) {
    if (arr1 === arr2) return true;
    if (Object.prototype.toString.call(arr1) !== '[object Array]' ||
        Object.prototype.toString.call(arr2) !== '[object Array]') {
        return false;
    }
    if (arr1.length !== arr2.length) return false;
    for (var i = 0, len = arr1.length; i < len; i++) {
        if (arr1[i] !== arr2[i]) return false;
    }
    return true;
}

function hasStateChanged(oldState, newState, type) {
    if (type === ChangeType.ARRAY) {
        return !isArrayEqual(oldState, newState);
    } else if (type === ChangeType.PRICE) {
        return oldState !== newState;
    }
    return false;
}

function updateState(context, type, action, params) {
    var updateHandlers = {
        order: function () {
            if (action === 'delete') {
                context.currentOrder = null;
                context.oldLevel = undefined;
            } else if (action === 'update') {
                if (!params.order || params.order.Price !== params.price) {
                    context.currentOrder = params.order;
                    context.oldLevel = params.price;
                }
            }
        },
        book: function () {
            if (action === 'update' && params.data) {
                context.P = params.data.Prices;
                context.Q = params.data.Quantities;
                context.current = params.data.Prices[0];
                context.lastQuantity = params.data.Quantities[0];
            }
        }
    };

    if (updateHandlers[type]) {
        updateHandlers[type]();
    }
    return context;
}

function updateContext(context, type, action, params) {
    updateState(context, type, action, params);
    return context;
}

// BookUtils
var BookUtils = (function () {

    function mapBookData(originalBook, config) {
        return {
            BID: extractPricesQuantities(originalBook.Bid, config.LEVELS.BID, true),
            ASK: extractPricesQuantities(originalBook.Ask, config.LEVELS.ASK, true)
        };
    }

    function updateBookContext(side, newBookSide, context) {
        return updateContext(context, 'book', 'update', { data: newBookSide });
    }

    function checkAndUpdateSideState(side, newBookSide, config) {
        var priceContext = UserContext.prices[side];
        var hasPriceChanged = StateManager.checkStateChange(priceContext.P, newBookSide.Prices, ChangeType.ARRAY, 'price');
        var hasQuantityChanged = StateManager.checkStateChange(priceContext.Q, newBookSide.Quantities, ChangeType.ARRAY, 'quantity');

        if (hasPriceChanged || hasQuantityChanged) {
            UserContext.prices[side] = updateBookContext(side, newBookSide, priceContext);
            return true;
        }
        return false;
    }

    function updateStateIfChanged(book, config) {
        var newBook = mapBookData(book, config);
        var changesDetected = false;

        for (var i = 0; i < config.SIDES.length; i++) {
            var side = config.SIDES[i];
            if (checkAndUpdateSideState(side, newBook[side], config)) {
                changesDetected = true;
            }
        }
        UserContext.book = newBook;
        return changesDetected;
    }

    return {
        getData: function (book, side, level, type) {
            return getSideData(book, side, level, type === 'Price' ? 'Prices' : 'Quantities');
        },
        getBookState: function () {
            return UserContext.prices;
        },
        mapBookData: mapBookData,
        hasChanges: updateStateIfChanged
    };
})();

// OrderStateManager
var OrderStateManager = (function () {
    function updateOrderContext(side, action, order, price) {
        return updateContext(UserContext.prices[side], 'order', action, { order: order, price: price });
    }

    function manageOrderState(action, params) {
        UserContext.order.current = updateOrderContext(params.side, action, params.order, params.price).currentOrder;
        return UserContext.order.current;
    }
    return {
        manageOrderState: manageOrderState
    };
})();

// OrderCreationUtils
var OrderCreationUtils = (function () {
    function createOrder(stock, price, quantity) {
        return OrderManager.Buy(stock, price, quantity);
    }

    function modifyOrder(order, newPrice) {
        return OrderManager.Modify(order.OrderCode, newPrice);
    }

    function cancelOrder(orderCode) {
        return OrderManager.Delete(orderCode);
    }

    return {
        createOrder: createOrder,
        modifyOrder: modifyOrder,
        cancelOrder: cancelOrder
    };
})();

// OrderProcessor
var OrderProcessor = (function (orderCreationUtils, orderStateManager, logger) {

    function handleOrderAction(actionType, params) {
        var orderResult;
        var logMessage;
        var logData;

        if (actionType === 'create') {
            orderResult = orderCreationUtils.createOrder(params.stock, params.price, params.size);
            logMessage = 'Order Action';
            logData = { action: 'inserito', orderCode: orderResult.OrderCode };
            orderStateManager.manageOrderState('update', { side: params.side, order: orderResult, price: params.price });
        } else if (actionType === 'modify') {
            orderResult = orderCreationUtils.modifyOrder(params.order, params.newPrice);
            logMessage = 'Order Action';
            logData = { action: 'modificato', orderCode: orderResult.OrderCode };
            orderStateManager.manageOrderState('update', { side: params.side, order: orderResult, price: params.newPrice });
        } else if (actionType === 'cancel') {
            orderCreationUtils.cancelOrder(params.orderCode);
            logMessage = 'Order Deleted';
            logData = { orderCode: params.orderCode };
            orderStateManager.manageOrderState('delete', { side: params.side, order: null, price: undefined });
        }

        logger.log(logMessage, logData, false, params.book, params.prices, UserContext.status.numOperazione);
        return orderResult;
    }

    function placeOrder(side, stock, price, book, prices, config) {
        var actionType = UserContext.order.current ? 'modify' : 'create';
        var params = {
            side: side,
            stock: stock,
            price: price,
            size: config.SIZE,
            order: UserContext.order.current,
            newPrice: price,
            book: book,
            prices: prices
        };
        return handleOrderAction(actionType, params);
    }

    function removeOrder(orderCode, book, prices, config) {
        var params = {
            side: config.ORDER_SIDE,
            orderCode: orderCode,
            book: book,
            prices: prices
        };
        handleOrderAction('cancel', params);
        return null;
    }

    return {
        placeOrder: placeOrder,
        removeOrder: removeOrder,
        handleOrderAction: handleOrderAction
    };
})(OrderCreationUtils, OrderStateManager, LogManager.getLogger());

// PriceHelper
var PriceHelper = {
    priceDirection: function (targetPrice, oldPrice) {
        return (targetPrice > oldPrice) - (targetPrice < oldPrice);
    }
};

// Funzione per determinare il livello del prezzo target
function getTargetPriceLevel(side, config) {
    return config.MONITORING_MODE === 'MONITORA_NLevels' ? config.LEVELS[side] - 1 : 0;
}

// OrderContextManager
var OrderContextManager = (function (logger, OrderProcessor, OrderStateManager) {

    function executeOrderCancellation(order, status, config) {
        var params = {
            side: config.ORDER_SIDE,
            orderCode: order.OrderCode,
            book: UserContext.book,
            prices: UserContext.prices
        };
        OrderProcessor.handleOrderAction('cancel', params);
        status.run = false;
    }

    function cancelCurrentOrder(status, config) {
        if (!UserContext.order.current) return false;
        executeOrderCancellation(UserContext.order.current, status, config);
        return true;
    }

    return {
        cancelCurrentOrder: cancelCurrentOrder
    };
})(LogManager.getLogger(), OrderProcessor, OrderStateManager);

// Funzioni di gestione degli eventi e del ciclo principale

function updatePrice(side, config, OrderProcessor, logger) {
    var book = UserContext.book;
    var targetPriceLevel = getTargetPriceLevel(side, config);
    var targetPrice = getSideData(book, side, targetPriceLevel, 'Prices');
    var oldPrice = UserContext.prices[side].oldLevel;

    if (!StateManager.checkStateChange(oldPrice, targetPrice, ChangeType.PRICE, 'price')) return;
    var orderPlaced = OrderProcessor.placeOrder(side, ps, targetPrice, book, UserContext.prices, config);
    handlePriceUpdate(orderPlaced, side, targetPriceLevel, targetPrice, oldPrice, logger);
}

function handlePriceUpdate(orderPlaced, side, targetPriceLevel, targetPrice, oldPrice, logger) {
    if (orderPlaced) {
        var priceDir = PriceHelper.priceDirection(targetPrice, oldPrice);
        logger.log('LevelChange', {
            lastLevel: targetPriceLevel + priceDir,
            priceLevel: targetPriceLevel
        }, true, UserContext.book, UserContext.prices, UserContext.status.numOperazione);
        UserContext.status.secondCount = 0;
        UserContext.status.numOperazione++;
    }
}

function isMaxOperationsReached(status, maxOperations) {
    return status.numOperazione >= maxOperations;
}

function manageOrderCancellation(config) {
    var maxOperationsReached = UserContext.status.numOperazione >= config.MAX_OPERATIONS;
    if (maxOperationsReached) {
        OrderContextManager.cancelCurrentOrder(UserContext.status, config);
    }
}

function processCycle(status, manageOrderCancellation, BookUtils, config, OrderProcessor, logger) {
    status.secondCount++;
    if (status.secondCount >= config.SECONDS) {
        status.secondCount = 0;
        manageOrderCancellation(config);
        if (status.run) {
            var updatedBook = BookUtils.mapBookData(Book(ps), config);
            UserContext.book = updatedBook;
            updatePrice(config.ORDER_SIDE, config, OrderProcessor, logger);
        }
    }
}

function onPrice(ctx, price, logger) {
    if (ctx.lastPrice !== price) {
        ctx.currentPrice = price;
        logger.logPriceChange(ctx);
    }
}

function onTime(ctx, manageOrderCancellation, BookUtils, config, OrderProcessor, logger) {
    processCycle(ctx.status, manageOrderCancellation, BookUtils, config, OrderProcessor, logger);
}

function onEvent(context, type, onPrice, onTime, manageOrderCancellation, BookUtils, config, OrderProcessor, logger) {
    if (type === TSEventType.Price) {
        onPrice(context, Val(ps, "LAST"), logger);
    } else if (type === TSEventType.Time) {
        onTime(context, manageOrderCancellation, BookUtils, config, OrderProcessor, logger);
    }
}

// Inizializzazione
function initializeUserContext(book, sideLevels, config) {
    var prices = {};
    for (var i = 0, len = config.SIDES.length; i < len; i++) {
        var side = config.SIDES[i];
        prices[side] = { P: [], Q: [], current: undefined, lastQuantity: undefined };
        if (book.hasOwnProperty(side)) {
            prices[side] = updateContext(prices[side], 'book', 'update', { data: book[side] });
        }
    }
    return {
        book: book,
        order: { current: null },
        status: { run: true, secondCount: 0, numOperazione: 0 },
        prices: prices,
        targetPriceLevels: sideLevels
    };
}

function setupTradeContext(OrderProcessor, BookUtils, calculateTick, logger, manageOrderCancellation, getTargetPriceLevelFunc, ps, config) {
    var book = Book(ps);
    var initialBook = BookUtils.mapBookData(book, config);
    var sideLevels = {
        BID: getTargetPriceLevelFunc('BID', config),
        ASK: getTargetPriceLevelFunc('ASK', config)
    };
    UserContext = initializeUserContext(initialBook, sideLevels, config);
    calculateTick(initialBook.BID.Prices, initialBook.ASK.Prices, config, logger);
    logger.initializeOrderAndLog(OrderProcessor, BookUtils, UserContext.book, UserContext.prices, config);
    processCycle(UserContext.status, manageOrderCancellation, BookUtils, config, OrderProcessor, logger);
}

// Factory per la gestione degli eventi di mercato
function marketEventFactory(ps, bookProvider, bookMapper, eventHandler, userContext, systemContext, onEvent, BookUtils, config, OrderProcessor, logger) {
    return function () {
        if (userContext.status.run) {
            BookUtils.hasChanges(Book(ps), config);
            onEvent(userContext, systemContext.EventType, onPrice, onTime, manageOrderCancellation, BookUtils, config, OrderProcessor, logger);
        }
    };
}

// Inizializzazione del logger
var logger = LogManager.getLogger();

// Punto di ingresso
if (SystemContext.IsFirst) {
    setupTradeContext(OrderProcessor, BookUtils, calculateTick, logger, manageOrderCancellation, getTargetPriceLevel, ps, CONFIG);
}

// Creazione e invocazione dell'istanza dell'evento di mercato
var marketEventInstance = marketEventFactory(ps, Book, BookUtils.mapBookData, onEvent, UserContext, SystemContext, onEvent, BookUtils, CONFIG, OrderProcessor, logger);

marketEventInstance();// 1. Configurazione
var CONFIG = {
    ORDER_SIDE: 'BID',
    SIZE: 1,
    MAX_OPERATIONS: 3,
    LEVELS: { BID: 6, ASK: 6 },
    SECONDS: 5,
    TICK: 1,
    MONITORING_MODE: 'MONITORA_NLevels',
    SIDES: ['BID', 'ASK'],
    TYPES: ['Price', 'Quantity']
};

// 2. ChangeType
var ChangeType = {
    ARRAY: 'array',
    PRICE: 'price'
};

// 3. LogManager
var LogManager = (function () {
    function MessageFormatter() {
        this.separator = "---------------------------------------------------\r\n";
        this.messages = {
            'Initialization Complete': "Inizializzazione completata:\nBid Price 0: {bidPrice}\nAsk Price 0: {askPrice}\n",
            'Order Action': 'Ordine {action}: {orderCode}\n',
            'Order Deleted': 'Ordine cancellato: {orderCode}\n',
            'LevelChange': "Livello da {lastLevel} all'attuale livello {priceLevel}\n",
            'PriceChange': "Prezzo da {previousPrice} all'attuale prezzo {currentPrice}\nOra: {currentTime}\r\n",
            'Operation Number': "Numero Operazione: {numOperazione}\r\n",
            'message': "{message}\r\n"
        };
    }

    MessageFormatter.prototype.format = function (template, data) {
        return template.replace(/{(\w+)}/g, function (match, key) {
            return data.hasOwnProperty(key) ? data[key] : match;
        });
    };

    function formatBookSide(bookSide, sideName, levels, separator) {
        var sideMessage = separator + sideName + ":\n";
        var pricesLength = bookSide.Prices.length;
        for (var level = 0; level < levels && pricesLength > level; level++) {
            sideMessage += "Level " + level + ": Price = " + bookSide.Prices[level] + ", Quantity = " + bookSide.Quantities[level] + "\n";
        }
        return sideMessage;
    }

    function formatBookState(book, prices, numOperazione, separator) {
        var formattedState = separator;
        formattedState += "Bid Price \tBid Quant.\tAsk Price \tAsk Quant.\r\n";
        formattedState += separator;
        formattedState += getSideData(book, 'BID', 0, 'Prices') + "\t\t" + getSideData(book, 'BID', 0, 'Quantities') + "\t\t";
        formattedState += prices.ASK.current + "\t\t" + prices.ASK.lastQuantity + "\r\n";
        formattedState += separator;
        formattedState += "Numero Operazione: " + numOperazione + "\r\n";
        formattedState += separator;
        return formattedState;
    }

    function Logger(formatter) {
        this.formatter = formatter;
    }

    Logger.prototype.log = function (type, data, includeFormattedState, book, prices, numOperazione) {
        if (!this.formatter.messages.hasOwnProperty(type)) return;
        var message = this.formatter.separator + this.formatter.format(this.formatter.messages[type], data || {});
        if (includeFormattedState !== false && (type === 'Initialization Complete' || type === 'LevelChange')) {
            message += formatBookState(book, prices, numOperazione, this.formatter.separator);
        }
        Log(message);
    };

    Logger.prototype.logFullBook = function (book, config) {
        var message = [
            formatBookSide(book.BID, 'BID', config.LEVELS.BID, this.formatter.separator),
            this.formatter.separator,
            formatBookSide(book.ASK, 'ASK', config.LEVELS.ASK, this.formatter.separator)
        ];
        Log(message.join(''));
    };

    Logger.prototype.initializeOrderAndLog = function (OrderProcessor, BookUtils, book, prices, config) {
        var initialPrice = BookUtils.getData(book, config.ORDER_SIDE, getTargetPriceLevel(config.ORDER_SIDE, config), 'Price');
        OrderProcessor.placeOrder(config.ORDER_SIDE, stock, initialPrice, book, prices, config);
        this.log('Initialization Complete', {
            bidPrice: prices.BID.current,
            askPrice: prices.ASK.current
        }, true, book, prices, UserContext.status.numOperazione);
        this.logFullBook(book, config);
    };

    Logger.prototype.logPriceChange = function (ctx) {
        this.log('PriceChange', {
            previousPrice: ctx.lastPrice,
            currentPrice: ctx.currentPrice,
            currentTime: Now().ToString("HH:mm:ss.ff")
        });
        ctx.lastPrice = ctx.currentPrice;
    };

    Logger.prototype.logTickCalculation = function (tick) {
        this.log('message', { message: 'Tick calcolato: ' + tick }, false);
    };

    var messageFormatter = new MessageFormatter();
    var logger = new Logger(messageFormatter);

    return {
        getLogger: function () {
            return logger;
        }
    };
})();

// 4. calculateTick
function calculateTick(bidPrices, askPrices, config, logger) {
    var MINIMUM_TICK = 0.005;
    if (!bidPrices || !askPrices || bidPrices.length < 2 || askPrices.length < 2) {
        logger.logTickCalculation('0');
        return 0;
    }
    var minDiff = Infinity;
    var len = Math.min(bidPrices.length, askPrices.length);
    for (var i = 0; i < len - 1; i++) {
        var bidDiff = Math.abs(bidPrices[i + 1] - bidPrices[i]);
        var askDiff = Math.abs(askPrices[i + 1] - askPrices[i]);
        if (bidDiff < minDiff && bidDiff >= MINIMUM_TICK) minDiff = bidDiff;
        if (askDiff < minDiff && askDiff >= MINIMUM_TICK) minDiff = askDiff;
        if (minDiff === MINIMUM_TICK) break;
    }
    if (minDiff === Infinity) {
        logger.logTickCalculation('0');
        return 0;
    }
    var tick = Math.round(minDiff * 1000) / 1000;
    logger.logTickCalculation(tick.toString());
    return tick;
}

// 5. StateManager
var StateManager = (function () {
    function checkStateChange(context, newState, changeType, type) {
        var hasChanged = hasStateChanged(context, newState, changeType);
        if (hasChanged) {
            context = updateContext(context, type, 'update', { data: newState });
        }
        return hasChanged;
    }

    return {
        checkStateChange: checkStateChange
    };
})();

// Funzioni di utility per la gestione del book
function getSideData(book, side, level, dataType) {
    if (typeof book === 'object' && book !== null && side in book && level >= 0) {
        var sideData = book[side];
        if (Object.prototype.toString.call(sideData[dataType]) === '[object Array]' && sideData[dataType].length > level) {
            return sideData[dataType][level];
        }
    }
    return undefined;
}

function extractPricesQuantities(source, levels, isFunc) {
    var prices, quantities;
    if (isFunc) {
        prices = new Array(levels);
        quantities = new Array(levels);
        for (var i = 0; i < levels; i++) {
            prices[i] = source.Price(i);
            quantities[i] = source.Quantity(i);
        }
    } else {
        prices = (source.Prices || []).slice(0, levels);
        quantities = (source.Quantities || []).slice(0, levels);
    }
    return { Prices: prices, Quantities: quantities };
}

function isArrayEqual(arr1, arr2) {
    if (arr1 === arr2) return true;
    if (Object.prototype.toString.call(arr1) !== '[object Array]' ||
        Object.prototype.toString.call(arr2) !== '[object Array]') {
        return false;
    }
    if (arr1.length !== arr2.length) return false;
    for (var i = 0, len = arr1.length; i < len; i++) {
        if (arr1[i] !== arr2[i]) return false;
    }
    return true;
}

function hasStateChanged(oldState, newState, type) {
    if (type === ChangeType.ARRAY) {
        return !isArrayEqual(oldState, newState);
    } else if (type === ChangeType.PRICE) {
        return oldState !== newState;
    }
    return false;
}

function updateState(context, type, action, params) {
    var updateHandlers = {
        order: function () {
            if (action === 'delete') {
                context.currentOrder = null;
                context.oldLevel = undefined;
            } else if (action === 'update') {
                if (!params.order || params.order.Price !== params.price) {
                    context.currentOrder = params.order;
                    context.oldLevel = params.price;
                }
            }
        },
        book: function () {
            if (action === 'update' && params.data) {
                context.P = params.data.Prices;
                context.Q = params.data.Quantities;
                context.current = params.data.Prices[0];
                context.lastQuantity = params.data.Quantities[0];
            }
        }
    };

    if (updateHandlers[type]) {
        updateHandlers[type]();
    }
    return context;
}

function updateContext(context, type, action, params) {
    updateState(context, type, action, params);
    return context;
}

// BookUtils
var BookUtils = (function () {

    function mapBookData(originalBook, config) {
        return {
            BID: extractPricesQuantities(originalBook.Bid, config.LEVELS.BID, true),
            ASK: extractPricesQuantities(originalBook.Ask, config.LEVELS.ASK, true)
        };
    }

    function updateBookContext(side, newBookSide, context) {
        return updateContext(context, 'book', 'update', { data: newBookSide });
    }

    function checkAndUpdateSideState(side, newBookSide, config) {
        var priceContext = UserContext.prices[side];
        var hasPriceChanged = StateManager.checkStateChange(priceContext.P, newBookSide.Prices, ChangeType.ARRAY, 'price');
        var hasQuantityChanged = StateManager.checkStateChange(priceContext.Q, newBookSide.Quantities, ChangeType.ARRAY, 'quantity');

        if (hasPriceChanged || hasQuantityChanged) {
            UserContext.prices[side] = updateBookContext(side, newBookSide, priceContext);
            return true;
        }
        return false;
    }

    function updateStateIfChanged(book, config) {
        var newBook = mapBookData(book, config);
        var changesDetected = false;

        for (var i = 0; i < config.SIDES.length; i++) {
            var side = config.SIDES[i];
            if (checkAndUpdateSideState(side, newBook[side], config)) {
                changesDetected = true;
            }
        }
        UserContext.book = newBook;
        return changesDetected;
    }

    return {
        getData: function (book, side, level, type) {
            return getSideData(book, side, level, type === 'Price' ? 'Prices' : 'Quantities');
        },
        getBookState: function () {
            return UserContext.prices;
        },
        mapBookData: mapBookData,
        hasChanges: updateStateIfChanged
    };
})();

// OrderStateManager
var OrderStateManager = (function () {
    function updateOrderContext(side, action, order, price) {
        return updateContext(UserContext.prices[side], 'order', action, { order: order, price: price });
    }

    function manageOrderState(action, params) {
        UserContext.order.current = updateOrderContext(params.side, action, params.order, params.price).currentOrder;
        return UserContext.order.current;
    }
    return {
        manageOrderState: manageOrderState
    };
})();

// OrderCreationUtils
var OrderCreationUtils = (function () {
    function createOrder(stock, price, quantity) {
        return OrderManager.Buy(stock, price, quantity);
    }

    function modifyOrder(order, newPrice) {
        return OrderManager.Modify(order.OrderCode, newPrice);
    }

    function cancelOrder(orderCode) {
        return OrderManager.Delete(orderCode);
    }

    return {
        createOrder: createOrder,
        modifyOrder: modifyOrder,
        cancelOrder: cancelOrder
    };
})();

// OrderProcessor
var OrderProcessor = (function (orderCreationUtils, orderStateManager, logger) {

    function handleOrderAction(actionType, params) {
        var orderResult;
        var logMessage;
        var logData;

        if (actionType === 'create') {
            orderResult = orderCreationUtils.createOrder(params.stock, params.price, params.size);
            logMessage = 'Order Action';
            logData = { action: 'inserito', orderCode: orderResult.OrderCode };
            orderStateManager.manageOrderState('update', { side: params.side, order: orderResult, price: params.price });
        } else if (actionType === 'modify') {
            orderResult = orderCreationUtils.modifyOrder(params.order, params.newPrice);
            logMessage = 'Order Action';
            logData = { action: 'modificato', orderCode: orderResult.OrderCode };
            orderStateManager.manageOrderState('update', { side: params.side, order: orderResult, price: params.newPrice });
        } else if (actionType === 'cancel') {
            orderCreationUtils.cancelOrder(params.orderCode);
            logMessage = 'Order Deleted';
            logData = { orderCode: params.orderCode };
            orderStateManager.manageOrderState('delete', { side: params.side, order: null, price: undefined });
        }

        logger.log(logMessage, logData, false, params.book, params.prices, UserContext.status.numOperazione);
        return orderResult;
    }

    function placeOrder(side, stock, price, book, prices, config) {
        var actionType = UserContext.order.current ? 'modify' : 'create';
        var params = {
            side: side,
            stock: stock,
            price: price,
            size: config.SIZE,
            order: UserContext.order.current,
            newPrice: price,
            book: book,
            prices: prices
        };
        return handleOrderAction(actionType, params);
    }

    function removeOrder(orderCode, book, prices, config) {
        var params = {
            side: config.ORDER_SIDE,
            orderCode: orderCode,
            book: book,
            prices: prices
        };
        handleOrderAction('cancel', params);
        return null;
    }

    return {
        placeOrder: placeOrder,
        removeOrder: removeOrder,
        handleOrderAction: handleOrderAction
    };
})(OrderCreationUtils, OrderStateManager, LogManager.getLogger());

// PriceHelper
var PriceHelper = {
    priceDirection: function (targetPrice, oldPrice) {
        return (targetPrice > oldPrice) - (targetPrice < oldPrice);
    }
};

// Funzione per determinare il livello del prezzo target
function getTargetPriceLevel(side, config) {
    return config.MONITORING_MODE === 'MONITORA_NLevels' ? config.LEVELS[side] - 1 : 0;
}

// OrderContextManager
var OrderContextManager = (function (logger, OrderProcessor, OrderStateManager) {

    function executeOrderCancellation(order, status, config) {
        var params = {
            side: config.ORDER_SIDE,
            orderCode: order.OrderCode,
            book: UserContext.book,
            prices: UserContext.prices
        };
        OrderProcessor.handleOrderAction('cancel', params);
        status.run = false;
    }

    function cancelCurrentOrder(status, config) {
        if (!UserContext.order.current) return false;
        executeOrderCancellation(UserContext.order.current, status, config);
        return true;
    }

    return {
        cancelCurrentOrder: cancelCurrentOrder
    };
})(LogManager.getLogger(), OrderProcessor, OrderStateManager);

// Funzioni di gestione degli eventi e del ciclo principale

function updatePrice(side, config, OrderProcessor, logger) {
    var book = UserContext.book;
    var targetPriceLevel = getTargetPriceLevel(side, config);
    var targetPrice = getSideData(book, side, targetPriceLevel, 'Prices');
    var oldPrice = UserContext.prices[side].oldLevel;

    if (!StateManager.checkStateChange(oldPrice, targetPrice, ChangeType.PRICE, 'price')) return;
    var orderPlaced = OrderProcessor.placeOrder(side, ps, targetPrice, book, UserContext.prices, config);
    handlePriceUpdate(orderPlaced, side, targetPriceLevel, targetPrice, oldPrice, logger);
}

function handlePriceUpdate(orderPlaced, side, targetPriceLevel, targetPrice, oldPrice, logger) {
    if (orderPlaced) {
        var priceDir = PriceHelper.priceDirection(targetPrice, oldPrice);
        logger.log('LevelChange', {
            lastLevel: targetPriceLevel + priceDir,
            priceLevel: targetPriceLevel
        }, true, UserContext.book, UserContext.prices, UserContext.status.numOperazione);
        UserContext.status.secondCount = 0;
        UserContext.status.numOperazione++;
    }
}

function isMaxOperationsReached(status, maxOperations) {
    return status.numOperazione >= maxOperations;
}

function manageOrderCancellation(config) {
    var maxOperationsReached = UserContext.status.numOperazione >= config.MAX_OPERATIONS;
    if (maxOperationsReached) {
        OrderContextManager.cancelCurrentOrder(UserContext.status, config);
    }
}

function processCycle(status, manageOrderCancellation, BookUtils, config, OrderProcessor, logger) {
    status.secondCount++;
    if (status.secondCount >= config.SECONDS) {
        status.secondCount = 0;
        manageOrderCancellation(config);
        if (status.run) {
            var updatedBook = BookUtils.mapBookData(Book(ps), config);
            UserContext.book = updatedBook;
            updatePrice(config.ORDER_SIDE, config, OrderProcessor, logger);
        }
    }
}

function onPrice(ctx, price, logger) {
    if (ctx.lastPrice !== price) {
        ctx.currentPrice = price;
        logger.logPriceChange(ctx);
    }
}

function onTime(ctx, manageOrderCancellation, BookUtils, config, OrderProcessor, logger) {
    processCycle(ctx.status, manageOrderCancellation, BookUtils, config, OrderProcessor, logger);
}

function onEvent(context, type, onPrice, onTime, manageOrderCancellation, BookUtils, config, OrderProcessor, logger) {
    if (type === TSEventType.Price) {
        onPrice(context, Val(ps, "LAST"), logger);
    } else if (type === TSEventType.Time) {
        onTime(context, manageOrderCancellation, BookUtils, config, OrderProcessor, logger);
    }
}

// Inizializzazione
function initializeUserContext(book, sideLevels, config) {
    var prices = {};
    for (var i = 0, len = config.SIDES.length; i < len; i++) {
        var side = config.SIDES[i];
        prices[side] = { P: [], Q: [], current: undefined, lastQuantity: undefined };
        if (book.hasOwnProperty(side)) {
            prices[side] = updateContext(prices[side], 'book', 'update', { data: book[side] });
        }
    }
    return {
        book: book,
        order: { current: null },
        status: { run: true, secondCount: 0, numOperazione: 0 },
        prices: prices,
        targetPriceLevels: sideLevels
    };
}

function setupTradeContext(OrderProcessor, BookUtils, calculateTick, logger, manageOrderCancellation, getTargetPriceLevelFunc, ps, config) {
    var book = Book(ps);
    var initialBook = BookUtils.mapBookData(book, config);
    var sideLevels = {
        BID: getTargetPriceLevelFunc('BID', config),
        ASK: getTargetPriceLevelFunc('ASK', config)
    };
    UserContext = initializeUserContext(initialBook, sideLevels, config);
    calculateTick(initialBook.BID.Prices, initialBook.ASK.Prices, config, logger);
    logger.initializeOrderAndLog(OrderProcessor, BookUtils, UserContext.book, UserContext.prices, config);
    processCycle(UserContext.status, manageOrderCancellation, BookUtils, config, OrderProcessor, logger);
}

// Factory per la gestione degli eventi di mercato
function marketEventFactory(ps, bookProvider, bookMapper, eventHandler, userContext, systemContext, onEvent, BookUtils, config, OrderProcessor, logger) {
    return function () {
        if (userContext.status.run) {
            BookUtils.hasChanges(Book(ps), config);
            onEvent(userContext, systemContext.EventType, onPrice, onTime, manageOrderCancellation, BookUtils, config, OrderProcessor, logger);
        }
    };
}

// Inizializzazione del logger
var logger = LogManager.getLogger();

// Punto di ingresso
if (SystemContext.IsFirst) {
    setupTradeContext(OrderProcessor, BookUtils, calculateTick, logger, manageOrderCancellation, getTargetPriceLevel, ps, CONFIG);
}

// Creazione e invocazione dell'istanza dell'evento di mercato
var marketEventInstance = marketEventFactory(ps, Book, BookUtils.mapBookData, onEvent, UserContext, SystemContext, onEvent, BookUtils, CONFIG, OrderProcessor, logger);

marketEventInstance();