var app = angular.module('plunker', ['ngRoute', 'ngAnimate', 'ngSanitize']);

app.controller('MainCtrl', function($scope) {
  $scope.pair = 'BTCUSD';
  $scope.twdusd = 29.3;
  $scope.ratio = 1.05;
  $scope.data = {};
  $scope.buyAtType = 'ask';
  $scope.sellAt=0;
  
  fetch();
  setInterval(fetch, 5000);
  $scope.inTwd = function(k) {
    return Number.parseFloat($scope.data[k] * $scope.twdusd).toFixed(3);
  }
  $scope.profitablePrice = function(k) {
    return Number.parseFloat($scope.data[k] * $scope.twdusd * $scope.ratio).toFixed(3);
  }
  
  $scope.updateSellAt= function(){
    $scope.sellAt=0
  }

  $scope.calcProfit = function(type) {
    var sellAt = Number.parseFloat($scope.sellAt).toFixed(3),
      buyAt = $scope.inTwd(type || 'ask');
    return (100 * (sellAt - buyAt) / buyAt).toFixed(3)
  }

  function fetch() {
    request('https://api.bitfinex.com/v1/pubticker/' + $scope.pair, function(er, response, body) {
      console.log(response.body)
      var data = JSON.parse(body);
      if (er)
        throw er;
      console.log(data);
      for (var k in data) {
        $scope.data[k] = data[k];
      }
      
      //update sellAt
    if ($scope.sellAt==0) $scope.sellAt = $scope.profitablePrice('ask');
      $scope.$digest();
    })
  }

});

// app.directive('numberFormat', function() {
//   return {
//     require: 'ngModel',
//     link: function(scope, elem, attr, ngModel) {
//       const amountOfDecimals = attr.step.split('.')[1].length;
//       ngModel.$formatters.unshift((val) => {
//         return parseFloat(val).toFixed(amountOfDecimals);
//       })

//     }
//   };
// })
<!DOCTYPE html>
<html ng-app="plunker">

<head>
  <meta charset="utf-8" />
  <title>Profit Calculator: Bitfinex</title>
  <script>
    document.write('<base href="' + document.location + '" />');
  </script>
  <link href="style.css" rel="stylesheet" />
  <script src="https://code.angularjs.org/1.6.2/angular.js"></script>
  <!-- By setting the version to snapshot (available for all modules), you can test with the latest master version -->
  <!--<script src="https://code.angularjs.org/snapshot/angular.js"></script>-->
  <script src="https://code.angularjs.org/1.6.2/angular-route.js"></script>
  <script src="https://code.angularjs.org/1.6.2/angular-animate.js"></script>
  <script src="https://code.angularjs.org/1.6.2/angular-sanitize.js"></script>
  <script src="request.js"></script>
  <script src="app.js"></script>
</head>

<body ng-controller="MainCtrl">
  TWD/USD:
  <input ng-model="twdusd"> <br>
  Ratio:
  <input ng-model="ratio">
  Coin:
  <select name="Pair" ng-model="pair" ng-change="updateSellAt()">
    <option value="BTCUSD">BTC</option>
    <option value="ETHUSD">ETH</option>
    <option value="BCHUSD">BCH</option>
    <option value="LTCUSD">LTC</option>
  </select>
  <h2>Bitfinex Price</h2>
  <p>mid: {{data.mid}} USD = {{inTwd('mid')}} TWD</p>
  <p>bid: {{data.bid}} USD = {{inTwd('bid')}} TWD</p>
  <p>ask: {{data.ask}} USD = {{inTwd('ask')}} TWD</p>
  <h2>Profitable Price</h2>
  <p>mid: {{profitablePrice('mid')}} TWD</p>
  <p>bid: {{profitablePrice('bid')}} TWD</p>
  <p>ask: {{profitablePrice('ask')}} TWD</p>
  <p>sell at <input ng-model="sellAt"> and buy at <select name="BuyAtType" ng-model="buyAtType">
    <option value="mid">mid</option>
    <option value="bid">bid</option>
    <option value="ask">ask</option>
  </select> will get you a {{calcProfit(buyAtType)}}% profit.</p>
</body>

</html>
/* Put your css in here */

var XHR = XMLHttpRequest

if (!XHR) throw new Error('missing XMLHttpRequest')

request.log = {

  'trace': noop, 'debug': noop, 'info': noop, 'warn': noop, 'error': noop

}



var DEFAULT_TIMEOUT = 3 * 60 * 1000 // 3 minutes



//

// request

//



function request(options, callback) {

  // The entry-point to the API: prep the options object and pass the real work to run_xhr.

  if(typeof callback !== 'function')

    throw new Error('Bad callback given: ' + callback)



  if(!options)

    throw new Error('No options given')



  var options_onResponse = options.onResponse; // Save this for later.



  if(typeof options === 'string')

    options = {'uri':options};

  else

    options = JSON.parse(JSON.stringify(options)); // Use a duplicate for mutating.



  options.onResponse = options_onResponse // And put it back.



  if (options.verbose) request.log = getLogger();



  if(options.url) {

    options.uri = options.url;

    delete options.url;

  }



  if(!options.uri && options.uri !== "")

    throw new Error("options.uri is a required argument");



  if(typeof options.uri != "string")

    throw new Error("options.uri must be a string");



  var unsupported_options = ['proxy', '_redirectsFollowed', 'maxRedirects', 'followRedirect']

  for (var i = 0; i < unsupported_options.length; i++)

    if(options[ unsupported_options[i] ])

      throw new Error("options." + unsupported_options[i] + " is not supported")



  options.callback = callback

  options.method = options.method || 'GET';

  options.headers = options.headers || {};

  options.body    = options.body || null

  options.timeout = options.timeout || request.DEFAULT_TIMEOUT



  if(options.headers.host)

    throw new Error("Options.headers.host is not supported");



  if(options.json) {

    options.headers.accept = options.headers.accept || 'application/json'

    if(options.method !== 'GET')

      options.headers['content-type'] = 'application/json'



    if(typeof options.json !== 'boolean')

      options.body = JSON.stringify(options.json)

    else if(typeof options.body !== 'string')

      options.body = JSON.stringify(options.body)

  }

  

  //BEGIN QS Hack

  var serialize = function(obj) {

    var str = [];

    for(var p in obj)

      if (obj.hasOwnProperty(p)) {

        str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));

      }

    return str.join("&");

  }

  

  if(options.qs){

    var qs = (typeof options.qs == 'string')? options.qs : serialize(options.qs);

    if(options.uri.indexOf('?') !== -1){ //no get params

        options.uri = options.uri+'&'+qs;

    }else{ //existing get params

        options.uri = options.uri+'?'+qs;

    }

  }

  //END QS Hack

  

  //BEGIN FORM Hack

  var multipart = function(obj) {

    //todo: support file type (useful?)

    var result = {};

    result.boundry = '-------------------------------'+Math.floor(Math.random()*1000000000);

    var lines = [];

    for(var p in obj){

        if (obj.hasOwnProperty(p)) {

            lines.push(

                '--'+result.boundry+"\n"+

                'Content-Disposition: form-data; name="'+p+'"'+"\n"+

                "\n"+

                obj[p]+"\n"

            );

        }

    }

    lines.push( '--'+result.boundry+'--' );

    result.body = lines.join('');

    result.length = result.body.length;

    result.type = 'multipart/form-data; boundary='+result.boundry;

    return result;

  }

  

  if(options.form){

    if(typeof options.form == 'string') throw('form name unsupported');

    if(options.method === 'POST'){

        var encoding = (options.encoding || 'application/x-www-form-urlencoded').toLowerCase();

        options.headers['content-type'] = encoding;

        switch(encoding){

            case 'application/x-www-form-urlencoded':

                options.body = serialize(options.form).replace(/%20/g, "+");

                break;

            case 'multipart/form-data':

                var multi = multipart(options.form);

                //options.headers['content-length'] = multi.length;

                options.body = multi.body;

                options.headers['content-type'] = multi.type;

                break;

            default : throw new Error('unsupported encoding:'+encoding);

        }

    }

  }

  //END FORM Hack



  // If onResponse is boolean true, call back immediately when the response is known,

  // not when the full request is complete.

  options.onResponse = options.onResponse || noop

  if(options.onResponse === true) {

    options.onResponse = callback

    options.callback = noop

  }



  // XXX Browsers do not like this.

  //if(options.body)

  //  options.headers['content-length'] = options.body.length;



  // HTTP basic authentication

  if(!options.headers.authorization && options.auth)

    options.headers.authorization = 'Basic ' + b64_enc(options.auth.username + ':' + options.auth.password);



  return run_xhr(options)

}



var req_seq = 0

function run_xhr(options) {

  var xhr = new XHR

    , timed_out = false

    , is_cors = is_crossDomain(options.uri)

    , supports_cors = ('withCredentials' in xhr)



  req_seq += 1

  xhr.seq_id = req_seq

  xhr.id = req_seq + ': ' + options.method + ' ' + options.uri

  xhr._id = xhr.id // I know I will type "_id" from habit all the time.



  if(is_cors && !supports_cors) {

    var cors_err = new Error('Browser does not support cross-origin request: ' + options.uri)

    cors_err.cors = 'unsupported'

    return options.callback(cors_err, xhr)

  }



  xhr.timeoutTimer = setTimeout(too_late, options.timeout)

  function too_late() {

    timed_out = true

    var er = new Error('ETIMEDOUT')

    er.code = 'ETIMEDOUT'

    er.duration = options.timeout



    request.log.error('Timeout', { 'id':xhr._id, 'milliseconds':options.timeout })

    return options.callback(er, xhr)

  }



  // Some states can be skipped over, so remember what is still incomplete.

  var did = {'response':false, 'loading':false, 'end':false}



  xhr.onreadystatechange = on_state_change

  xhr.open(options.method, options.uri, true) // asynchronous

  if(is_cors)

    xhr.withCredentials = !! options.withCredentials

  xhr.send(options.body)

  return xhr



  function on_state_change(event) {

    if(timed_out)

      return request.log.debug('Ignoring timed out state change', {'state':xhr.readyState, 'id':xhr.id})



    request.log.debug('State change', {'state':xhr.readyState, 'id':xhr.id, 'timed_out':timed_out})



    if(xhr.readyState === XHR.OPENED) {

      request.log.debug('Request started', {'id':xhr.id})

      for (var key in options.headers)

        xhr.setRequestHeader(key, options.headers[key])

    }



    else if(xhr.readyState === XHR.HEADERS_RECEIVED)

      on_response()



    else if(xhr.readyState === XHR.LOADING) {

      on_response()

      on_loading()

    }



    else if(xhr.readyState === XHR.DONE) {

      on_response()

      on_loading()

      on_end()

    }

  }



  function on_response() {

    if(did.response)

      return



    did.response = true

    request.log.debug('Got response', {'id':xhr.id, 'status':xhr.status})

    clearTimeout(xhr.timeoutTimer)

    xhr.statusCode = xhr.status // Node request compatibility



    // Detect failed CORS requests.

    if(is_cors && xhr.statusCode == 0) {

      var cors_err = new Error('CORS request rejected: ' + options.uri)

      cors_err.cors = 'rejected'



      // Do not process this request further.

      did.loading = true

      did.end = true



      return options.callback(cors_err, xhr)

    }



    options.onResponse(null, xhr)

  }



  function on_loading() {

    if(did.loading)

      return



    did.loading = true

    request.log.debug('Response body loading', {'id':xhr.id})

    // TODO: Maybe simulate "data" events by watching xhr.responseText

  }



  function on_end() {

    if(did.end)

      return



    did.end = true

    request.log.debug('Request done', {'id':xhr.id})



    xhr.body = xhr.responseText

    if(options.json) {

      try        { xhr.body = JSON.parse(xhr.responseText) }

      catch (er) { return options.callback(er, xhr)        }

    }



    options.callback(null, xhr, xhr.body)

  }



} // request



request.withCredentials = false;

request.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT;



//

// defaults

//



request.defaults = function(options, requester) {

  var def = function (method) {

    var d = function (params, callback) {

      if(typeof params === 'string')

        params = {'uri': params};

      else {

        params = JSON.parse(JSON.stringify(params));

      }

      for (var i in options) {

        if (params[i] === undefined) params[i] = options[i]

      }

      return method(params, callback)

    }

    return d

  }

  var de = def(request)

  de.get = def(request.get)

  de.post = def(request.post)

  de.put = def(request.put)

  de.head = def(request.head)

  return de

}



//

// HTTP method shortcuts

//



var shortcuts = [ 'get', 'put', 'post', 'head' ];

shortcuts.forEach(function(shortcut) {

  var method = shortcut.toUpperCase();

  var func   = shortcut.toLowerCase();



  request[func] = function(opts) {

    if(typeof opts === 'string')

      opts = {'method':method, 'uri':opts};

    else {

      opts = JSON.parse(JSON.stringify(opts));

      opts.method = method;

    }



    var args = [opts].concat(Array.prototype.slice.apply(arguments, [1]));

    return request.apply(this, args);

  }

})



//

// CouchDB shortcut

//



request.couch = function(options, callback) {

  if(typeof options === 'string')

    options = {'uri':options}



  // Just use the request API to do JSON.

  options.json = true

  if(options.body)

    options.json = options.body

  delete options.body



  callback = callback || noop



  var xhr = request(options, couch_handler)

  return xhr



  function couch_handler(er, resp, body) {

    if(er)

      return callback(er, resp, body)



    if((resp.statusCode < 200 || resp.statusCode > 299) && body.error) {

      // The body is a Couch JSON object indicating the error.

      er = new Error('CouchDB error: ' + (body.error.reason || body.error.error))

      for (var key in body)

        er[key] = body[key]

      return callback(er, resp, body);

    }



    return callback(er, resp, body);

  }

}



//

// Utility

//



function noop() {}



function getLogger() {

  var logger = {}

    , levels = ['trace', 'debug', 'info', 'warn', 'error']

    , level, i



  for(i = 0; i < levels.length; i++) {

    level = levels[i]



    logger[level] = noop

    if(typeof console !== 'undefined' && console && console[level])

      logger[level] = formatted(console, level)

  }



  return logger

}



function formatted(obj, method) {

  return formatted_logger



  function formatted_logger(str, context) {

    if(typeof context === 'object')

      str += ' ' + JSON.stringify(context)



    return obj[method].call(obj, str)

  }

}



// Return whether a URL is a cross-domain request.

function is_crossDomain(url) {

  var rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/



  // jQuery #8138, IE may throw an exception when accessing

  // a field from window.location if document.domain has been set

  var ajaxLocation

  try { ajaxLocation = location.href }

  catch (e) {

    // Use the href attribute of an A element since IE will modify it given document.location

    ajaxLocation = document.createElement( "a" );

    ajaxLocation.href = "";

    ajaxLocation = ajaxLocation.href;

  }



  var ajaxLocParts = rurl.exec(ajaxLocation.toLowerCase()) || []

    , parts = rurl.exec(url.toLowerCase() )



  var result = !!(

    parts &&

    (  parts[1] != ajaxLocParts[1]

    || parts[2] != ajaxLocParts[2]

    || (parts[3] || (parts[1] === "http:" ? 80 : 443)) != (ajaxLocParts[3] || (ajaxLocParts[1] === "http:" ? 80 : 443))

    )

  )



  //console.debug('is_crossDomain('+url+') -> ' + result)

  return result

}



// MIT License from http://phpjs.org/functions/base64_encode:358

function b64_enc (data) {

    // Encodes string using MIME base64 algorithm

    var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

    var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc="", tmp_arr = [];



    if (!data) {

        return data;

    }



    // assume utf8 data

    // data = this.utf8_encode(data+'');



    do { // pack three octets into four hexets

        o1 = data.charCodeAt(i++);

        o2 = data.charCodeAt(i++);

        o3 = data.charCodeAt(i++);



        bits = o1<<16 | o2<<8 | o3;



        h1 = bits>>18 & 0x3f;

        h2 = bits>>12 & 0x3f;

        h3 = bits>>6 & 0x3f;

        h4 = bits & 0x3f;



        // use hexets to index into b64, and append result to encoded string

        tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);

    } while (i < data.length);



    enc = tmp_arr.join('');



    switch (data.length % 3) {

        case 1:

            enc = enc.slice(0, -2) + '==';

        break;

        case 2:

            enc = enc.slice(0, -1) + '=';

        break;

    }



    return enc;

}