// ==UserScript==
// @name         IG Trading Simulator
// @version      1.1 (11-05-15)
// @description  IG Trading Simulator
// @author       M1A1
// @include      http://www.ig.com/pricestream/*
// @require      https://code.jquery.com/jquery-1.10.2.min.js
// @require      http://code.highcharts.com/highcharts.js
// @require      https://cdn.datatables.net/1.10.7/js/jquery.dataTables.js
// @require      https://rawgit.com/kayalshri/tableExport.jquery.plugin/master/tableExport.js
// @require      https://rawgit.com/kayalshri/tableExport.jquery.plugin/master/jquery.base64.js
// @require      https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js
// @resource     jqDT_CSS  https://cdn.datatables.net/1.10.7/css/jquery.dataTables.css
// @resource     jqBS_CSS  https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css
// @grant        GM_addStyle
// @grant        GM_getResourceText

// ==/UserScript==

console.log("Started");

var jqDT_CssSrc = GM_getResourceText ("jqDT_CSS");
var jqBS_CssSrc = GM_getResourceText ("jqBS_CSS");

GM_addStyle (jqDT_CssSrc);
GM_addStyle (jqBS_CssSrc);

// add style
function addStyle(css) {
    var head = document.head || document.getElementsByTagName('head')[0];
    if (head) {
        var style = document.createElement("style");
        style.type = "text/css";
        style.appendChild(document.createTextNode(css));
        head.appendChild(style);
    } // end if
} // end of function 

// Create element
function ce(n) { return document.createElement(n); }

function insertAfter(referenceNode, newNode) {
    referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}

// Local storage
function LS_get(key){ return localStorage.getItem("trade_" + key); }
function LS_set(key, value){ localStorage.setItem("trade_" + key, value); }
function LS_rm (key){        localStorage.removeItem("trade_" + key); }

// 
function lpad(num) {
    return ("00" + num).slice(-2);
}

// Clock
function get_hhmmss() {
    var tm = new Date();
    var tm_h = tm.getHours().toString();
    var tm_m = tm.getMinutes().toString();
    var tm_d = tm.getSeconds().toString();
    return lpad(tm_h) + ":" + lpad(tm_m) + ":" + lpad(tm_d); 
}

// Global
var position, 
// figure out if this is moz || IE because they use documentElement
el = (navigator.userAgent.indexOf('Firefox') != -1 || navigator.userAgent.indexOf('MSIE') != -1) ? document.documentElement : document.body;

var nb_trade=0;
var price_in,price_out;
var in_time,out_time;
var in_timestmap,out_timestmap;
var qty,qty_signed;
var myTick; // timer
var position_entered=0; // flag
var trd_div;
var dd,mx;

// Executed every 0.1 sec
function tick() {
    
    var dur = (Date.now() - in_timestamp)/1000;
    dur = Math.round(dur*10)/10;
    document.querySelector("div.duration").innerHTML = dur.toFixed(1);
    
    // DAX = item#2
    var bid = document.querySelector('td.t2 div[item="2"]');
    var ask = document.querySelector('td.t3 div[item="2"]');
    
    // Actual/out price
    var price = (qty==1) ? Number(bid.innerHTML) : Number(ask.innerHTML);
    price = price.toFixed(1);
        
    var pl = qty * (price - price_in);
    pl = Math.round(pl*10)/10;
   
    dd = Math.min(pl,dd);
    mx = Math.max(pl,mx);
    
    dd = dd.toFixed(1);
    mx = mx.toFixed(1);
    pl = pl.toFixed(1);
    
    //console.log(dd,mx,pl);
    
    document.querySelector("div.dd").innerHTML = dd;
    document.querySelector("div.mx").innerHTML = mx;
        
    var trd_pl = document.querySelector("div.pl");
    trd_pl.innerHTML = pl.toString();
    
    // Colorize : blue/red/white
    trd_pl.className = 'pl'; // flat = white
    if (pl>0) {trd_pl.className = 'pl blue';}
    if (pl<0) {trd_pl.className = 'pl red';}
    
    // update out-time and out-price
    document.querySelector("div.time-out").innerHTML  = get_hhmmss();
    document.querySelector("div.price-out").innerHTML = price.toString();

}


function get_in_out(action) {
    
    // Only one position allowed
    if ((position_entered>0) && (action=="buy") && (qty>0)) {return false;}
    if ((position_entered>0) && (action=="sell") && (qty<0)) {return false;}
    
    // DAX = item#2
    var bid = document.querySelector('td.t2 div[item="2"]');
    var ask = document.querySelector('td.t3 div[item="2"]');
    
    var now_timestamp = Date.now();
    var now_time = get_hhmmss();
    
    // Out price, depends on trade direction
    var action_price = (action=="sell") ? Number(bid.innerHTML) :  Number(ask.innerHTML); 
    action_price = action_price.toFixed(1);
    
    if (position_entered==0) {
        // === TRADE IN  ===
        position_entered = 1;
        qty = (action=="sell") ? -1 : 1;
        price_in = action_price;
        in_time = now_time;
        in_timestamp = now_timestamp;
        
        dd=0;
        mx=0;
        
        // Create a new row
        trd_div = ce('div');
        trd_div.setAttribute('id',in_timestamp);
        trd_div.className = "trd";
        
        // Add "+" when on the buy side
        qty_signed = (qty>0) ? "+1" : "-1";        
        nb_trade++;
        
        trd_div.innerHTML = "<div class='num'>"+nb_trade+"</div><div class='qty'>" + qty_signed + "</div><div class='time-in'>" + in_time + "</div><div class='time-out'></div><div class='price'>" + price_in.toString() + "</div><div class='price-out'></div></div><div class='duration'></div><div class='pl'></div><div class='dd'></div><div class='mx'></div><div class='close'>Close</div>";
      //  document.body.appendChild(trd_div);
        // Last trade at the top (IG like)
        var el_summary = document.querySelector(".header-list");
        insertAfter(el_summary,trd_div);
        
        // Create close button
        var btn_close = document.querySelector("div.close");
        if (qty>0) {btn_close.addEventListener('click', function() { get_in_out("sell");}  , false);}
        if (qty<0) {btn_close.addEventListener('click', function() { get_in_out("buy");}  , false);}
        
        // Simulate LightStreamer Ticks with a 100ms update
        myTick = setInterval(function () {tick();}, 100); 
    }
    else
    {
        // === TRADE OUT ===
        clearInterval(myTick);
        position_entered = 0;
        
        price_out = action_price;
        out_time = now_time;
        
        // Update trade row
        // PL
        var pl = qty*(price_out - price_in);
        pl = Math.round(pl*1000000)/1000000;
        pl = pl.toFixed(1);
        
        // Duration (in seconds)
        var dur = (Date.now() - in_timestamp)/1000;
        dur = Math.round(dur*10)/10;
        dur = dur.toFixed(1);
        
        // Find last trade row (with id/timestamp)
        trd_div = document.getElementById(in_timestamp);
        // Add a "+" ?
        qty_signed = (qty>0) ? "+1" : "-1";
        // flat :|
        if (pl==0) {trd_div.innerHTML = "<div class='num'>"+nb_trade+"</div><div class='qty'>" + qty_signed + "</div><div>" + in_time + "</div><div>" + out_time + "</div><div class='price'>" + price_in.toString() + "</div><div class='price'>" + price_out.toString() + "</div><div>" + dur.toString() + "</div><div class='tpl     '>" + pl.toString()+ "</div><div class='tdd'>" + dd.toString()+ "</div><div class='tmx'>" + mx.toString()+ "</div>";}
        // gain :)
        if (pl>0)  {trd_div.innerHTML = "<div class='num'>"+nb_trade+"</div><div class='qty'>" + qty_signed + "</div><div>" + in_time + "</div><div>" + out_time + "</div><div class='price'>" + price_in.toString() + "</div><div class='price'>" + price_out.toString() + "</div><div>" + dur.toString() + "</div><div class='tpl blue'>" + pl.toString()+ "</div><div class='tdd'>" + dd.toString()+ "</div><div class='tmx'>" + mx.toString()+ "</div>";}
        // loss :(
        if (pl<0)  {trd_div.innerHTML = "<div class='num'>"+nb_trade+"</div><div class='qty'>" + qty_signed + "</div><div>" + in_time + "</div><div>" + out_time + "</div><div class='price'>" + price_in.toString() + "</div><div class='price'>" + price_out.toString() + "</div><div>" + dur.toString() + "</div><div class='tpl red'>"  + pl.toString()+ "</div><div class='tdd'>" + dd.toString()+ "</div><div class='tmx'>" + mx.toString()+ "</div>";}
        
        // === Update Summary Row ===
        // Trades count
        var trade_list = document.querySelectorAll("div.trd");
        var trade_number = trade_list.length-1; // excluding header (with .trd class for aligment)
        
        // Total PL
        var pl_list = document.querySelectorAll("div.tpl");
        var total_pl = 0;
        var pct = 0;
        for (var i = 1; i < pl_list.length; i++) {
            total_pl+=Number(pl_list[i].innerHTML);
            pct += (Number(pl_list[i].innerHTML) >= 0) ? 1 : 0;
        }
        total_pl = Math.round(total_pl*10)/10;
        total_pl = total_pl.toFixed(1);
        
        // % Win rate
        pct = 100 * pct / trade_number;
        pct = Math.round(pct*10)/10;
        pct = pct.toFixed(1);
        
        document.querySelector(".summary").innerHTML = "<div>Summary :</div><div>"+trade_number.toString()+" trade(s)</div><div>"+total_pl.toString()+" pts</div><div>"+pct.toString()+" %</div>";
        
        // Save to localstorage for history and future use
        var trd = new Object();
        trd.timestamp  = in_timestamp;
        trd.qty        = qty_signed;
        trd.time_in    = in_time;
        trd.time_out   = out_time;
        trd.price_in   = price_in;
        trd.price_out  = price_out;
        trd.duration   = dur;
        trd.pl         = pl;    
        trd.dd         = dd; 
        trd.mx         = mx; 
        LS_set(in_timestamp,JSON.stringify(trd));
        
    }
        
}

//
function my_round(in_float) {
    var out_float = Math.round(in_float*100)/100;
    return out_float.toFixed(2);
}

//
function timeConverter(UNIX_timestamp){
  var a = new Date(UNIX_timestamp*1);
  var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  var year = a.getFullYear();
  var month = months[a.getMonth()];
  month = a.getMonth()+1;
  var date = a.getDate();
  var time = lpad(date) + '/' + lpad(month) + '/' + year ;
  return time;
}

// load trades sotred in locastorage
function get_trades() {
    
    var res = [];
    var sum=0;
    for (i = 0; i <= localStorage.length - 1; i++) {
        var key  = localStorage.key(i);
        var data = localStorage.getItem(key);
        var obj  = null;

        try {
            obj = JSON.parse(data);
        } catch (e) {
           // console.log('invalid json in localstorage : '+e);
        }
        
        if (key.indexOf("trade_")>=0) {
            obj.index = i+1;
            obj.truedate  = timeConverter(obj.timestamp);
            obj.price_in  = Number(obj.price_in).toFixed(1);
            obj.price_out = Number(obj.price_out).toFixed(1);
            obj.duration  = Number(obj.duration).toFixed(1);
            
            if (obj.dd!==undefined) {
                obj.dd        = Number(obj.dd).toFixed(1);
                obj.mx        = Number(obj.mx).toFixed(1);
            }
            else
            {
                obj.dd='';
                obj.mx='';
            }
            obj.pl        = Number(obj.pl).toFixed(1);
            sum+=Number(obj.pl);
            obj.equity = Math.round(sum*10)/10;
            obj.equity = obj.equity.toFixed(1);
            obj.time_in =">"+obj.time_in;
            obj.sens = (Number(obj.qty)>0) ? "A" : "V"; 
            res.push(obj);
        }
    }
    return res;
}


// build stats from an array of PL
function build_stats(pl_list) {
    
    // pl_list = array of pl
    var count=0,count_win=0,count_loss=0,avg_gain=0,avg_win=0,avg_loss=0,pct=0,sum_win=0,sum_loss=0;
    
    count = pl_list.length;
    for (var i = 0; i <= pl_list.length; i++) {
        
        count_win  += (pl_list[i]>=0) ? 1:0;
        count_loss += (pl_list[i]<0)  ? 1:0;
        
        sum_win  += (pl_list[i]>=0) ? pl_list[i]:0;
        sum_loss += (pl_list[i]<0)  ? pl_list[i]:0;
           
        }
    
    var stat = new Object();
    stat.count        = count;
    stat.total_gain   = my_round(sum_win+sum_loss);
    stat.count_win    = count_win;
    stat.count_loss   = count_loss;
    
    stat.pct          = my_round(count_win/count*100);
    
    stat.avg_gain     = my_round((sum_win+sum_loss)/count);
    stat.avg_win      = my_round(sum_win/count_win);
    stat.avg_loss     = my_round(sum_loss/count_loss);
    
    stat.ratio        = my_round(-(sum_win/count_win)/(sum_loss/count_loss));
       
    return stat;
    
}

// display statistics
function draw_stats (statistics) {
    // reset
    $('div.chart-stat div.text-right').html("-");
    if (statistics.count>0) {
        $('div.chart-stat div.s1').html(statistics.count);
        $('div.chart-stat div.s2').html(statistics.total_gain);
        $('div.chart-stat div.s3').html(statistics.avg_gain);
        $('div.chart-stat div.s4').html(statistics.pct);
        $('div.chart-stat div.s5').html(statistics.avg_win);
        $('div.chart-stat div.s6').html(statistics.avg_loss);
        $('div.chart-stat div.s7').html(statistics.ratio);
    }       
}

// update equity column
function update_equity() {
    
    var sum_filter=0;   
    var t_pl = [];
    $.each( $( "#trade_table td.tab-pl" ), function( key, value ) {
        sum_filter+=Number($(value).html());
        t_pl.push(Number($(value).html()));
    }); 

    $.each( $( "#trade_table tbody tr[role='row']" ), function( key, value ) {
        var tr = $(value); 
        var td_1 = tr.find("td.tab-pl");
        var td_2 = tr.find("td.equity");
        td_2.html(sum_filter.toFixed(1));
        sum_filter = sum_filter - Number(td_1.html());  
    });   
    
    return t_pl;
    
}

// draw_chart (from equity column)
function draw_equity() {
    
    var arr=[];
    var cat=[];
    $.each( $( "#trade_table tbody td.equity" ), function( index, value ) {
               var td = $(value); 
               arr.push(Number(td.html()));
               cat.push(index+1);
          });   
    
    arr.reverse();
    
    $('#equity-chart').highcharts({
                chart: { type: 'areaspline', animation: {duration: 1000}, zoomType: 'xy'  },
                title: {text: 'Equity'},
                xAxis: {categories: cat},
                yAxis: { title: {text: 'Points'} },
        series: [{name :'', marker: {radius:2}, data: arr}]
            });
}

// fill table (from trades stored in localstorage)
function draw_table() {
    
    // load
    var trd_array = get_trades();
    
    $('#trade_table').DataTable( {
                destroy: true,
                language: {
                    search:         "Filtrer&nbsp;:",
                    lengthMenu:     "Afficher _MENU_ &eacute;l&eacute;ments",
                    info:           "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
                    infoEmpty:      "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ments",
                    infoFiltered:   "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
                    infoPostFix:    "",
                    zeroRecords:    "Aucun &eacute;l&eacute;ment &agrave; afficher",
                    emptyTable:     "Aucune donne disponible dans le tableau"
                },
                order: [[ 0, 'desc' ]],
                paging: false,
                data: trd_array,
                columns: [
                    { data: 'timestamp', visible: false, className: 'ts-id' },
                    { data: 'truedate' },
                    { data: 'sens'},
                    { data: 'qty', className: 'dt-right' },
                    { data: 'time_in' , className: 'dt-right' },
                    { data: 'time_out' , className: 'dt-right' },
                    { data: 'price_in' , className: 'dt-right' },
                    { data: 'price_out' , className: 'dt-right' },
                    { data: 'duration' , className: 'dt-right' },
                    { data: 'pl' , className: 'dt-right tab-pl' },
                    { data: 'dd' , className: 'dt-right' },
                    { data: 'mx' , className: 'dt-right' },
                    { data: 'equity' , className: 'dt-right equity' }
                ]  
        } );
                    
    // for future refresh (filtering)
    $('#trade_table').on( 'draw.dt', function () {  
        
        // update equity column
        var pl_array = update_equity();
          
        // draw chart
        draw_equity();
                             
        // display statitics
        var statistics = build_stats(pl_array);
        
        // display statitics
        draw_stats(statistics);
        
        //
        $('td.tab-pl').on('click', function (e) {
            var v = e.currentTarget;
            var tr = $(v).parent();
            var id = tr.find("td.tsid").html();
            
            LS_rm(id);
            console.log("trade_"+id);
        });
  
    } );
                    
}

// css
var s=''; 
	 
s+='body {margin : 5px 0 0 5px;}';                   
s+='div.trd {border:1px solid #AAA;margin: 2px;padding: 0px;font-size: 12px;font-family: Arial;width:772px;}';
s+='div.trd div {display:inline-block;width:75px;border-right: 1px solid #AAA;text-align: center;height: 22px;line-height: 22px;}';
s+='#tableContainer {margin-bottom: 15px;}';
s+='div[item="2"][field="2"] {border:2px solid #AAA;margin-left: 2px}';
s+='div[item="2"][field="1"] {border:2px solid #AAA;}';
s+='div[item="2"][field="2"]:hover {border:2px solid #0095d6;font-weight:bold;cursor:pointer;}';
s+='div[item="2"][field="1"]:hover {border:2px solid #d92d27;font-weight:bold;cursor:pointer;}';
s+='tbody div {border: 2px solid transparent; margin-left: 2px;}';
//
s+='div.close:hover {font-weight:bold;cursor:pointer;background:#EEE;}';
s+='div.trd div.close {border-right: 0px;width:82px;}';
// pl 
s+='div.pl.red,div.tpl.red   {background:#e83f3c;}';
s+='div.pl.blue,div.tpl.blue {background:#1896d3;}';
//
s+='div.trd div.qty,div.trd div.num  {width:40px;}';
// summary
s+='div.summary {border:1px solid #AAA;margin: 2px;padding: 0px;font-size: 12px;font-family: Arial;background:#EEE;margin-top:8px;margin-bottom:8px;width:620px;}';
s+='div.summary div {display:inline-block;width:80px;border-right: 1px solid #AAA;text-align: center;height: 22px;line-height: 22px;}';
//
s+='.price,.price-out {font-weight:bold;}';
//
s+='div.header-list {color: #555; background: #d3d5d0; font-weight: bold;}';

// history
s+='div.trd-history {width:855px;margin-top:20px;}';
   
// override
s+='.dataTables_wrapper .dataTables_filter {float:left;}';
s+='table.dataTable.cell-border tbody td {border-right: 1px solid #ddd;}';

s+='g.highcharts-legend {display:none;}';
s+='div.highcharts-container text:nth-of-type(2) {display:none;}';

s+='div.chart-stat {width:180px;  font-size: 12px; display:inline-block;margin: 50px 0px 0px 20px;}';
s+='#equity-chart  {float:left;}';  
s+='#btn-excel {top: 82px;z-index: 1000;}';
s+='#btn-refresh {top: 82px;z-index: 1000;left: -10px;}';
s+='td.tab-pl {font-weight : bold;}';
        
// Go for it
addStyle(''+s);

if(el){ 
    
        // HIDE non-indice rows
        var tr;
        for (var i = 4; i <= 9; i++) {
            tr = document.querySelector('tr:nth-of-type('+i+')');
            if (tr!==null) tr.style.display = "none";
        }
           
        // Define buy and sell "button" = DAX Bid & Ask
        var bt_buy  = document.querySelector('div[item="2"][field="2"]');
        var bt_sell = document.querySelector('div[item="2"][field="1"]');
    
        bt_buy.addEventListener('click', function()  { get_in_out("buy");}  , false);
        bt_sell.addEventListener('click', function() { get_in_out("sell");} , false);

        // Create summary div
        var summary = ce('div');
        summary.className='summary';
        summary.innerHTML = "<div>Summary:</div><div>-</div><div>-</div><div>-</div>";
        document.body.appendChild(summary);
        
        // Create header div
        var header = ce('div');
        header.className = 'trd header-list';
        header.innerHTML = "<div class='num'>#</div><div class='qty'>Qt</div><div>Entre</div><div>Sortie</div><div class='price'>Ouverture</div><div class='price'>Cloture</div><div>Dure (sec)</div><div class='tpl'>Points</div><div class='tdd'>Min.</div><div class='tmx'>Max.</div>" ;
        document.body.appendChild(header);
    
        // History Panel div           
        var div_history = ce('div');
        div_history.className = 'trd-history';
        var init_html =''; 
        init_html += "<div class='panel panel-primary'><div class='panel-heading'><h4 class='panel-title'><a  data-toggle='collapse' href='#collapseHistory' aria-expanded='false'>Historique complet</a></h4></div>";
        init_html += "<div class='collapse' id='collapseHistory'>";
        init_html += "<div class='panel-body'>";
        // Chart
        init_html += "<div id='equity-chart' style='height: 250px;width:600px;'></div>";
        // Statistics
        init_html += "<div class='chart-stat'>";
        init_html += "<div class='row'><div class='col-md-8'>Nombre</div>          <div class='col-md-4 text-right s1'>-</div></div>";
        init_html += "<div class='row'><div class='col-md-8'>Gain/Perte</div>      <div class='col-md-4 text-right s2'>-</div></div>";
        init_html += "<div class='row'><div class='col-md-8'>Moyenne / Trade</div> <div class='col-md-4 text-right s3'>-</div></div>";
        init_html += "<div class='row'><div class='col-md-8'>% Gagnant</div>       <div class='col-md-4 text-right s4'>-</div></div>";
        init_html += "<div class='row'><div class='col-md-8'>Moyenne Gagnants</div><div class='col-md-4 text-right s5'>-</div></div>";
        init_html += "<div class='row'><div class='col-md-8'>Moyenne Perdants</div><div class='col-md-4 text-right s6'>-</div></div>";
        init_html += "<div class='row'><div class='col-md-8'>Ratio Gain/Perte</div><div class='col-md-4 text-right s7'>-</div></div>";
        init_html += "</div>";
        // Buttons
        init_html += "<div class='btn-group pull-right'><button id='btn-excel'   class='btn btn-info btn-xs'>Export Excel</button></div>";
        init_html += "<div class='btn-group pull-right'><button id='btn-refresh' class='btn btn-success btn-xs'>Actualiser</button></div>";
        // Table
        init_html += "<table id='trade_table'  class='hover display cell-border dt-right' cellspacing='0' width='100%'>";
        init_html += "<thead><tr><th data-graph-skip='1'>TS</th><th data-graph-skip='1'>Date</th><th data-graph-skip='1'>Sens</th><th data-graph-skip='1'>Qt</th><th data-graph-skip='1'>Entre</th><th data-graph-skip='1'>Sortie</th><th data-graph-skip='1'>Ouverture</th><th data-graph-skip='1'>Cloture</th><th data-graph-skip='1'>Dure</th><th>PL</th><th data-graph-skip='1'>Min.</th><th data-graph-skip='1'>Max.</th><th data-graph-skip='1'>Equity</th>";
        init_html += "</tr></thead><tbody></tbody></table>";
        //
        init_html += "</div></div></div>";
                                        
        div_history.innerHTML = init_html;           
        document.body.appendChild(div_history);
                    
        var bt_export = document.querySelector('#btn-excel');
        bt_export.addEventListener('click', function() { $('#trade_table').tableExport({type:'excel',escape:'false'});} , false);  
    
        $('#btn-refresh').on('click', function() {
            draw_table();
        });
            
        // triggered when panel is opened
        $('#collapseHistory').on('shown.bs.collapse', function () {
            
            // fill table
            draw_table();
            
            // update equity column
            var pl_array = update_equity();

            // draw chart
            draw_equity();

            // display statitics
            var statistics = build_stats(pl_array);

            // display statitics
            draw_stats(statistics);

        });
 
}

