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 benissimo ad accorpare 3 funzioni in 2, o 4 in 2 o 3, in accordo ai principi SOLID 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(stock, Par1, Secondi, ps, systemContext, safeContext) che e’ preassegnata



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 CHANGE_TYPE = {
    ARRAY: 'array',
    PRICE: 'price'
};

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 hasStateChanged(oldState, newState, type) {
    if (type === CHANGE_TYPE.PRICE) {
        return oldState !== newState;
    } else if (type === CHANGE_TYPE.ARRAY) {
        if (oldState === newState) return false;
        if (oldState === null || newState === null) return true;
        if (oldState.length !== newState.length) return true;
        for (var i = 0, len = oldState.length; i < len; i++) {
            if (oldState[i] !== newState[i]) return true;
        }
        return false;
    }
    return false;
}

function updateContextWithHandlers(context, type, action, params) {
    switch (type) {
        case 'order':
            updateOrderContext(context, action, params);
            break;
        case 'book':
            updateBookContext(context, action, params);
            break;
        default:
            break;
    }
    return context;
}

function updateOrderContext(context, action, params) {
    updateContext(context, action, params, 'order');
}

function updateBookContext(context, action, params) {
    updateContext(context, action, params, 'book');
}

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

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

    function updateBookContext(side, newBookSide, context) {
        return updateContextFields(context, newBookSide);
    }

    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++) {
            var side = sides[i];
            var hasChanged = StateManager.checkStateChange(UserContext.prices[side], newBook[side].Prices, CHANGE_TYPE.ARRAY, 'price') ||
                StateManager.checkStateChange(UserContext.prices[side], newBook[side].Quantities, CHANGE_TYPE.ARRAY, 'quantity');
            if (hasChanged) {
                updateContextFields(UserContext.prices[side], newBook[side]);
                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 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, logger) {

    function handleOrderAction(actionType, params, userContext) {
        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 };
            manageOrderState('update', { side: params.side, order: orderResult, price: params.price }, userContext);
        } else if (actionType === 'modify') {
            orderResult = orderCreationUtils.modifyOrder(params.order, params.newPrice);
            logMessage = 'Order Action';
            logData = { action: 'modificato', orderCode: orderResult.OrderCode };
            manageOrderState('update', { side: params.side, order: orderResult, price: params.newPrice }, userContext);
        } else if (actionType === 'cancel') {
            orderResult = orderCreationUtils.cancelOrder(params.orderCode);
            logMessage = 'Order Deleted';
            logData = { orderCode: params.orderCode };
            manageOrderState('delete', { side: params.side, order: null, price: undefined }, userContext);
        }

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

    function manageOrderState(action, params, userContext) {
        updateContextWithHandlers(userContext.prices[params.side], 'order', action, params);
        userContext.order.current = userContext.prices[params.side].currentOrder;
        return userContext.order.current;
    }

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

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

    return {
        placeOrder: placeOrder,
        removeOrder: removeOrder,
        handleOrderAction: handleOrderAction
    };
})(OrderCreationUtils, 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, UserContext) {

    function executeOrderCancellation(order, status, config) {
        var params = {
            side: config.ORDER_SIDE,
            orderCode: order.OrderCode,
            book: UserContext.book,
            prices: UserContext.prices
        };
        OrderProcessor.handleOrderAction('cancel', params, UserContext);
        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, UserContext);

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, CHANGE_TYPE.PRICE, 'price')) return;
    var orderPlaced = OrderProcessor.placeOrder(side, ps, targetPrice, book, UserContext.prices, config, UserContext);

    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 createSideContext() {
    return { P: [], Q: [], current: undefined, lastQuantity: undefined };
}

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