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. Non usare ne’ i metodi “some” ne’ i cicli “for each”
:




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 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 = hasStateChanged(priceContext.P, newBookSide.Prices, ChangeType.ARRAY);
        var hasQuantityChanged = hasStateChanged(priceContext.Q, newBookSide.Quantities, ChangeType.ARRAY);

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

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

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 placeOrder(side, stock, price, book, prices, config) {
        var newOrder = UserContext.order.current
            ? orderCreationUtils.modifyOrder(UserContext.order.current, price)
            : orderCreationUtils.createOrder(stock, price, config.SIZE);

        logger.log('Order Action', { action: 'inserito', orderCode: newOrder.OrderCode }, false, book, prices, UserContext.status.numOperazione);
        return orderStateManager.manageOrderState('update', { side: side, order: newOrder, price: price });
    }

    function removeOrder(orderCode, book, prices, config) {
        orderCreationUtils.cancelOrder(orderCode);
        logger.log('Order Deleted', { orderCode: orderCode }, false, book, prices, UserContext.status.numOperazione);
        return orderStateManager.manageOrderState('delete', { side: config.ORDER_SIDE, order: null, price: undefined });
    }

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

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

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

    function executeOrderCancellation(order, status, config) {
        OrderProcessor.removeOrder(order.OrderCode, UserContext.book, UserContext.prices, config);
        logger.log('Order Deleted', { orderCode: order.OrderCode }, false, UserContext.book, UserContext.prices, UserContext.status.numOperazione);
        status.run = false;
        OrderStateManager.manageOrderState('delete', { side: config.ORDER_SIDE, order: null, price: undefined });
    }

function cancelCurrentOrder(status, config) {
    if (UserContext.order.current) {
        executeOrderCancellation(UserContext.order.current, status, config);
        return true;
    }
    return false;
}
    return {
        cancelCurrentOrder: cancelCurrentOrder
    };
})(LogManager.getLogger(), OrderProcessor, OrderStateManager);

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

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; // Verifica del tipo
    }
    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 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 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;
}

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 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 (!hasStateChanged(targetPrice, oldPrice, ChangeType.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] = 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);
}

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