Analizza questo codice SCRITTO IN ES5 (Non voglio ES6), alla ricerca delle principali duplicazioni nella gestione degli stati indicando PER OGNI FUNZIONE o VARIABILE il PRIMA, cioe' la funzione o variabile originale, e il DOPO, con le modifiche da te proposte ma evitando di scrivere // …resto del codice, in quanto avrei il piacere di vedere indicato il codice completo anche per le parti ripetute. Ti invito a 1 non accorpare 2 funzioni in 1, perche’ cio’ violerebbe il principo n.1 SRP di Solid, ma potrai benissimo accorpare 3 funzioni in 2, o 4 in 2 o 3, sia nuove o preesistenti, allo scopo di trasferire responsabilita’ tra funzioni allo scopo di eliminare duplicazioni nella gestione degli stati. 2 Non usare ne’ i metodi “some” ne’ i cicli “for each” 3 Non modificare la function Init

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()
    {

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']
};

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

var LogManager = (function (logFunction) {
    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);
        }
        logFunction(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)
        ];
        logFunction(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;
        }
    };
})(Log); 

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 maxIndex = Math.min(bidPrices.length, askPrices.length); // Compattazione
    for (var i = 0; i < maxIndex - 1; i++) {
        var bidDiff = Math.abs(bidPrices[i + 1] - bidPrices[i]);
        var askDiff = Math.abs(askPrices[i + 1] - askPrices[i]);
        minDiff = Math.min(minDiff, bidDiff >= MINIMUM_TICK ? bidDiff : minDiff, askDiff >= MINIMUM_TICK ? askDiff : minDiff);
        if (minDiff === MINIMUM_TICK) break;
    }
    if (minDiff === Infinity) {
        logger.logTickCalculation('0');
        return 0;
    }
    var tick = minDiff.toFixed(3);
    logger.logTickCalculation(tick);
    return Number(tick); 
}

var StateManager = (function () {
    function checkStateChange(context, newState, changeType, type) {
        var hasChanged = hasStateChanged(context, newState, changeType);
        if (hasChanged) {
            if (type === 'price') {
                context.P = newState;
            } else if (type === 'quantity') {
                context.Q = newState;
            }
            context.current = newState[0];
            context.lastQuantity = newState[0];
        }
        return hasChanged;
    }
    return {
        checkStateChange: checkStateChange
    };
})();

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 = new Array(levels);
    var quantities = new Array(levels);
    if (isFunc) {
        for (var i = 0; i < levels; i++) {
            prices[i] = source.Price(i);
            quantities[i] = source.Quantity(i);
        }
    } else {
        prices = source.Prices && source.Prices.length > 0 ? source.Prices.slice(0, levels) : new Array(levels).fill(undefined);
        quantities = source.Quantities && source.Quantities.length > 0 ? source.Quantities.slice(0, levels) : new Array(levels).fill(undefined);
    }
    return { Prices: prices, Quantities: quantities };
}

function isArrayEqual(arr1, arr2) {
    if (arr1 === arr2) return true;
    if (arr1 === null || arr2 === null) return false;
    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) {
    switch (type) {
        case ChangeType.ARRAY:
            return !isArrayEqual(oldState, newState);
        case ChangeType.PRICE:
            return oldState !== newState;
        default:
            return false;
    }
}

function updateContextWithHandlers(context, type, action, params) {
    var updateHandlers = {
        order: handleOrderUpdate,
        book: handleBookUpdate
    };

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

function handleOrderUpdate(context, action, params, userContext) { 
    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;
        }
    }
}

function handleBookUpdate(context, action, params, userContext) {
    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];
    }
}

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) {
        context.P = newBookSide.Prices;
        context.Q = newBookSide.Quantities;
        context.current = newBookSide.Prices[0];
        context.lastQuantity = newBookSide.Quantities[0];
        return context;
    }

    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;

var sides = config.SIDES;
for (var i = 0, len = sides.length; i < len; i++) {
    if (checkAndUpdateSideState(sides[i], newBook[sides[i]], 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
    };
})();

var OrderStateManager = (function () {
    function updateOrderContext(side, action, order, price) {
        return updateContextWithHandlers(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
    };
})();

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
    };
})();

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());

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

function getTargetPriceLevel(side, config) {
    return config.MONITORING_MODE === 'MONITORA_NLevels' ? config.LEVELS[side] - 1 : 0;
}

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);

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);
    }
}

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] = updateContextWithHandlers(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);
}

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);
        }
    };
}

var logger = LogManager.getLogger();

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

var marketEventInstance = marketEventFactory(ps, Book, BookUtils.mapBookData, onEvent, UserContext, SystemContext, onEvent, BookUtils, CONFIG, OrderProcessor, logger);

marketEventInstance();