Bloomberg Trading Game Bot

AutoTrader for bloomberg

// ==UserScript==
// @name         Bloomberg Trading Game Bot
// @namespace    http://tampermonkey.net/
// @version      9.0
// @description  AutoTrader for bloomberg
// @match        *://www.bloomberg.com/features/2015-stock-chart-trading-game/*
// @grant        none
// @run-at       document-start
// @license      CC0
// ==/UserScript==

(function() {
    'use strict';

    const origAddEvent = EventTarget.prototype.addEventListener;
    let handlers = {
        mousedown: [],
        mouseup: [],
        touchstart: [],
        touchend: []
    };

    EventTarget.prototype.addEventListener = function(type, handler, opts) {
        if (['mousedown', 'mouseup', 'touchstart', 'touchend'].includes(type)) {
            handlers[type].push({ el: this, handler, opts });
        }
        return origAddEvent.call(this, type, handler, opts);
    };

    setTimeout(() => {
        let lvl = null;
        let trading = false;
        let holding = false;
        let bPrice = null;
        let bTime = null;
        let prices = [];
        let trends = [];
        let learning = true;
        let learnStart = null;
        let pattern = null;

        function chkLevel() {
            const t = document.querySelector('.ticker');
            if (t && t.textContent) {
                const nLvl = t.textContent.trim();
                if (nLvl !== lvl && nLvl !== '') {
                    lvl = nLvl;
                    holding = false;
                    bPrice = null;
                    bTime = null;
                    prices = [];
                    trends = [];
                    learning = true;
                    learnStart = Date.now();
                    pattern = null;
                    if (!trading) trading = true;
                }
            }
        }

        setInterval(chkLevel, 500);

        function getCash() {
            const c = document.querySelector('.cash .net.liquid');
            if (c) {
                const txt = c.textContent.replace(/[^0-9.]/g, '');
                return parseFloat(txt) || 500;
            }
            return 500;
        }

        function getPrice() {
            const y = document.querySelector('.y-mark');
            if (y) {
                const t = y.getAttribute('transform');
                const m = t.match(/translate\(([\d.]+),([\d.]+)\)/);
                if (m) return parseFloat(m[2]);
            }
            return getCash();
        }

        function fire(evt) {
            const h = handlers[evt];
            if (h.length === 0) return;

            h.forEach(({ el, handler }) => {
                try {
                    let x = 500, y = 500;
                    if (el && el.getBoundingClientRect) {
                        const r = el.getBoundingClientRect();
                        x = r.left + r.width / 2;
                        y = r.top + r.height / 2;
                    }

                    const fake = {
                        type: evt,
                        target: el,
                        currentTarget: el,
                        clientX: x,
                        clientY: y,
                        button: 0,
                        buttons: 1,
                        preventDefault: () => {},
                        stopPropagation: () => {},
                        isTrusted: true
                    };
                    handler.call(el, fake);
                } catch (e) {}
            });
        }

        function analyze() {
            if (trends.length < 40) return null;

            const f30 = trends.slice(0, 30);
            const n20 = trends.slice(30, 50);

            const e = f30.slice(0, 15).reduce((a, b) => a + b, 0) / 15;
            const m = f30.slice(15, 30).reduce((a, b) => a + b, 0) / 15;
            const l = n20.reduce((a, b) => a + b, 0) / 20;

            let vol = 0;
            for (let i = 1; i < f30.length; i++) {
                vol += Math.abs(f30[i] - f30[i-1]);
            }
            vol = vol / f30.length;

            const et = e - m;
            const mt = m - l;

            let p = {
                type: 'unknown',
                drop: false,
                rise: false,
                vol: vol > 3,
                em: et,
                mm: mt
            };

            if (et < -5 && mt > 3) {
                p.type = 'dip-first';
                p.drop = true;
                p.rise = true;
            } else if (et > 5) {
                p.type = 'rise-first';
                p.drop = false;
                p.rise = true;
            } else if (p.vol) {
                p.type = 'volatile';
            } else {
                p.type = 'steady';
            }
            return p;
        }

        function holdTime() {
            if (!pattern) return 15000;
            switch(pattern.type) {
                case 'dip-first': return 20000;
                case 'rise-first': return 18000;
                case 'volatile': return 12000;
                case 'steady': return 18000;
                default: return 15000;
            }
        }

        function target() {
            if (!pattern) return 50;
            switch(pattern.type) {
                case 'dip-first': return 80;
                case 'rise-first': return 60;
                case 'volatile': return 40;
                case 'steady': return 50;
                default: return 50;
            }
        }

        function trend() {
            if (trends.length < 25) return 'unknown';
            const rec = trends.slice(-25);
            const o = rec.slice(0, 15).reduce((a, b) => a + b, 0) / 15;
            const n = rec.slice(-10).reduce((a, b) => a + b, 0) / 10;
            const d = o - n;
            if (d < -4) return 'downward';
            else if (d > 4) return 'upward';
            return 'sideways';
        }

        function reversal() {
            if (prices.length < 6 || trends.length < 25) return false;
            const rec = prices.slice(-6);
            const t = trend();
            if (t !== 'downward') return false;
            const vr = rec.slice(-3);
            return vr[2] < vr[1] && vr[1] < vr[0];
        }

        function shouldBuy() {
            if (learning) return false;
            if (prices.length < 5 || trends.length < 25) return false;

            const t = trend();
            const rev = reversal();

            if (pattern) {
                if (pattern.type === 'dip-first') {
                    if (rev) return true;
                    return false;
                }
                if (pattern.type === 'rise-first') {
                    if (t === 'upward' || rev) return true;
                }
                if (pattern.type === 'volatile') {
                    const r3 = prices.slice(-3);
                    if (r3[2] < r3[1] && r3[1] < r3[0]) return true;
                }
                if (pattern.type === 'steady') {
                    if (t === 'upward') return true;
                }
            }

            if (t === 'downward' && !rev) return false;

            const r5 = prices.slice(-5);
            let up = 0;
            let tot = 0;
            for (let i = 1; i < r5.length; i++) {
                const mv = r5[i-1] - r5[i];
                tot += mv;
                if (mv > 0) up++;
            }
            return up >= 3 && tot > 2;
        }

        function buy() {
            if (holding) return;
            fire('mousedown');
            fire('touchstart');
            holding = true;
            bPrice = getPrice();
            bTime = Date.now();
        }

        function sell() {
            if (!holding) return;
            fire('mousedown');
            fire('touchstart');
            setTimeout(() => {
                fire('mouseup');
                fire('touchend');
            }, 50);
            holding = false;
            bPrice = null;
            bTime = null;
        }

        function tick() {
            const p = getPrice();
            prices.push(p);
            trends.push(p);
            if (prices.length > 10) prices.shift();
            if (trends.length > 100) trends.shift();

            if (learning && learnStart) {
                const lt = Date.now() - learnStart;
                if (lt >= 4000 && trends.length >= 40) {
                    pattern = analyze();
                    learning = false;
                }
                return;
            }

            const maxHold = holdTime();

            if (holding && bTime && (Date.now() - bTime) >= maxHold) {
                sell();
                return;
            }

            if (holding) {
                const curr = getPrice();
                const chg = bPrice - curr;
                const ht = Date.now() - bTime;
                const profit = curr < bPrice;
                const tgt = target();
                const minHold = maxHold * 0.7;

                if (ht < minHold) {
                    if (!profit && Math.abs(chg) > 100) sell();
                    return;
                }

                if (profit && chg > tgt) {
                    sell();
                } else if (profit && chg > tgt * 0.6 && ht > maxHold * 0.9) {
                    sell();
                } else if (profit && chg > 20 && ht > maxHold * 0.85) {
                    sell();
                } else if (!profit && Math.abs(chg) > 100) {
                    sell();
                }
            } else {
                if (shouldBuy()) buy();
            }
        }

        setTimeout(() => {
            chkLevel();
            learnStart = Date.now();
        }, 2000);
        setInterval(tick, 100);
    }, 3000);
})();