Skip to content

Instantly share code, notes, and snippets.

@Stoner19
Created March 1, 2016 18:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Stoner19/cd7703810ce32b25056f to your computer and use it in GitHub Desktop.
Save Stoner19/cd7703810ce32b25056f to your computer and use it in GitHub Desktop.
Crypto-Exchange Visualizer
body.container(ng-app="exchangeApp", ng-controller="MainCtrl", ng-class="{'blink-sell':changeBlinkSell, 'blink-buy':changeBlinkBuy}", ng-show="exchange.pair")
.row
.col-sm-8
h2.text-oswald {{exchange.title}}
=' '
small {{exchange.pair.base.name}} : {{exchange.pair.quote.name}}
=' '
span.label.label-live.label-success(ng-show="exchange.isOnline()") LIVE
span.label.label-live.label-danger(ng-hide="exchange.isOnline()") OFFLINE
.row.small.last-rates(ng-class="{frozen: exchange.syncing}")
.col-xs-4
label Last Trade:
div(ng-bind="exchange.formatBase(exchange.trades.lines[0].rate)")
.col-xs-4
label Ask:
div(ng-bind="exchange.formatBase(exchange.sells.lines[0].rate)")
.col-xs-4
label Bid:
div(ng-bind="exchange.formatBase(exchange.buys.lines[0].rate)")
.col-sm-4.text-right.header-buttons
.btn-group(ng-show="exchange.isOnline()")
button.btn.btn-danger(ng-click="exchange.socketClose()")
span.glyphicon.glyphicon-off
.btn-group(ng-hide="exchange.isOnline()")
button.btn.btn-success(ng-click="exchange.sync()")
span.glyphicon.glyphicon-off
.btn-group
.btn-group(dropdown)
button.btn.btn-primary.dropdown-toggle(dropdown-toggle)
strong {{exchange.pair.format('-')}}
=' '
span.caret
ul.dropdown-menu.dropdown-menu-right.scrollable-menu(role="menu")
li(ng-repeat="market in exchange.markets")
a(href="#", ng-click="switchPair($event, market)", ng-bind="market.format('-')")
.row
.col-sm-12
label
input(type="checkbox", ng-model="windowBlink")
=' '
| Window Flash
=' / '
ae-alarm-dropdown(ng-model="alarmSystem", ae-exchange="exchange")
.row
.col-sm-6
h4(ng-class="{frozen: exchange.syncing}") Asks ({{exchange.sells.lines.length}})
small.pull-right(ng-bind="exchange.formatQuote(exchange.sells.total(),3)")
ae-order-book(ng-model="exchange.sells", pair="exchange.pair", frozen="exchange.syncing", line-count="obMaxLength", calculator-sell="calculator.sell", calculator-buy="calculator.buy")
.col-sm-6
h4(ng-class="{frozen: exchange.syncing}") Bids ({{exchange.buys.lines.length}})
small.pull-right(ng-bind="exchange.formatBase(exchange.buys.depth(),3)")
ae-order-book(ng-model="exchange.buys", pair="exchange.pair", frozen="exchange.syncing", line-count="obMaxLength", calculator-sell="calculator.sell", calculator-buy="calculator.buy")
.row
.col-md-6
.row
.col-xs-12.col-sm-4
h4(ng-class="{frozen: exchange.syncing}") Trade History
div
small(ng-bind="exchange.trades|bearBull")
div.small
label.small Last 200 trade volume:
=' '
span.small(ng-bind="exchange.formatBase(exchange.trades.totalSold+exchange.trades.totalBought)")
.col-xs-12.col-sm-8
.chart-wrapper(ng-show="exchange.trades.lines.length")
d3-trade-history(chart-ctrl="exchange", chart-width="auto", chart-height="50", ng-class="{frozen: exchange.syncing}")
.clearfix
ae-trade-history(ng-model="exchange.trades", frozen="exchange.syncing", pair="exchange.pair", line-count="200")
.col-md-6
h4 Activity Log
=' '
small Last {{exchange.events.maxLength}}
ae-event-log(ng-model="exchange.events")
.row(ng-repeat="alarm in alarmSystem.alarms")
.col-sm-12
h4 Alarm {{$index + 1}} log
ae-event-log(ng-model="alarm.log")
script(type="text/ng-template", id="/alarm-dropdown.tpl")
span(ng-class="{'text-success': model.hasActiveAlarms, 'text-muted': !model.hasActiveAlarms}", ng-if="model.hasAlarms")
span.glyphicon.glyphicon-bell
=' '
span(ng-show="model.hasActiveAlarms")
span.text-danger(ng-bind="model.numberAlarmsActive|number")
=' of {{model.numberAlarms|number}} active. '
span(ng-hide="model.hasActiveAlarms")
| {{model.numberAlarms|number}}
=' / '
span.text-muted(ng-hide="!!temporaryAlarm")
a.text-success(href="", ng-click="$event.preventDefault(); createNewAlarm()")
span.glyphicon.glyphicon-plus
=' '
| New Alarm
='{{model.hasAlarms ? " / " : ""}}'
span(ng-show="model.hasAlarms")
a.text-danger(href="", ng-click="$event.preventDefault(); model.clear()")
span.glyphicon.glyphicon-trash
=' '
| Clear Alarms
.well(ng-show="!!temporaryAlarm")
h3 New Price Alarm
form.form(name="newAlarmForm", ng-submit="saveNewAlarm(newAlarmForm)")
dl
dt Function
dd
select.form-control(ng-options="o[0] as o[1] for o in alarmFunctions.items", ng-model="temporaryAlarm.condition.name", required)
dl(ng-if="showAlarmPrice()")
dt Price
dd
input.form-control(type="number", min="0.00000001", step="0.00000001", ng-model="temporaryAlarm.condition.args[0]", required)
dl(ng-if="showAlarmChange()")
dt Percent Change
dd
input.form-control(type="number", min="0.00000001", step="0.00000001", ng-model="temporaryAlarm.condition.args[0]", required)
dl(ng-if="showAlarmChange()")
dt Change over time-frame (milliseconds)
dd
input.form-control(type="number", min="0", ng-model="temporaryAlarm.condition.args[1]", required)
dl
dt Maximum duration of the alarm
=' '
small.text-muted (in milliseconds, 0 = till end of event)
dd
input.form-control(type="number", min="0", ng-model="temporaryAlarm.maxDuration")
dl
dt Audio File
dd.input-group
select.form-control(ng-options="o[0] as o[1] for o in audioFiles.items", ng-model="temporaryAlarm.audioFile", required)
.input-group-btn
button.btn.btn-default(type="button", ng-click="previewAudio()", ng-disabled="!temporaryAlarm.audioFile|| isPreviewPlaying||!temporaryAlarm.audioPreview.canPlay")
span.glyphicon.glyphicon-play
dl
dt
input(type="checkbox", ng-model="temporaryAlarm.looped")
=' '
| Loop the audio file
dl
dd.text-right
.btn-group
button.btn.btn-danger(type="button", ng-click="cancelNewAlarm()") Cancel
button.btn.btn-success(type="submit", ng-disabled="!newAlarmForm.$valid") Save
script(type="text/ng-template", id="/order-book.tpl")
.panel.panel-default.panel-condensed(ng-class="{frozen: frozen}", ng-show="model.lines.length > 0")
div
d3-depth(book="model", chart-width="auto", chart-height="50", is-sell="isSellBook()")
.panel-body.small
.flex-grid.flex-header.flex-head-scrollable
.flex-rate.text-right Rate ({{pair.base.symbol}})
.flex-1.text-right Amount ({{pair.quote.symbol}})
.flex-1.text-right Value ({{pair.base.symbol}})
.scrollable-body
.flex-grid.flex-body(ng-repeat="order in model.summarized.slice(0,lineCount)")
.flex-rate.text-right.col-rate(ng-bind-html="formatNumHtml(order.rate, model.summaryPrecision)", ng-click="setCalculatorTarget(order.rate)")
.flex-1.text-right(ng-bind-html="formatNumHtml(order.amount, pair.quote.precision)")
.flex-1.text-right(ng-bind-html="formatNumHtml(order.value, pair.base.precision)")
.panel-footer.small
.flex-grid.flex-header
.flex-rate.text-right Rate ({{pair.base.symbol}})
.flex-1.text-right Sum ({{pair.quote.symbol}})
.flex-1.text-right Sum ({{pair.base.symbol}})
.flex-grid.flex-body(ng-repeat="bid in model.walls")
.flex-rate.text-right.col-rate(ng-bind-html="formatNumHtml(bid.rate, pair.base.precision)", title="{{pair.quote.format(bid.amount)}} / {{pair.base.format(bid.value)}}")
.flex-1.text-right(ng-bind-html="formatNumHtml(bid.sumAmount, 3)")
.flex-1.text-right(ng-bind-html="formatNumHtml(bid.sumValue, 3)")
.footer-control(ng-show="showCalculator")
dl
dt Amount of {{pair.quote.symbol}} to {{isBuyBook() ? 'buy' : 'sell'}}:
dd
input.form-control(type="number", ng-model="myCalculator().amount", min="0.001", ng-model-options="modelOptions")
dl
dt Highest {{pair.base.symbol}} bid price:
=' '
label
input(type="checkbox", ng-model="myCalculator().liquidation", ng-model-options="modelOptions")
=' '
| Until {{isBuyBook() ? 'bought up' : 'sold off'}}
dd
input.form-control(type="number", ng-model="myCalculator().targetPrice", ng-disabled="myCalculator().liquidation", min="0.00000001", ng-model-options="modelOptions")
.footer-control(ng-show="hasCalculatedResults()")
div(ng-if="isBuyBook()")
p {{myCalculator().results.numTrades|number}} trade(s) would occur.
=' '
span(ng-show="myCalculator().results.sumTradeValue > 0") A total of
=' '
span.text-success {{pair.quote.format(myCalculator().results.sumTradeAmount)}}
=' '
| would be received immediately for a total cost of
=' '
span.text-danger {{pair.base.format(myCalculator().results.sumTradeValue)}}
| .
=' '
span(ng-show="myCalculator().results.remainder > 0") A total of
=' '
span.text-danger {{pair.quote.format(myCalculator().results.remainder)}}
=' '
| would remain on order at price {{pair.base.format(myCalculator().targetPrice)}}.
p(ng-show="myCalculator().results.numTrades > 1") The lowest price paid would be {{pair.base.format(myCalculator().results.minTradeRate)}} and the highest would be {{pair.base.format(myCalculator().results.maxTradeRate)}}
div(ng-if="isSellBook()")
p {{myCalculator().results.numTrades|number}} trade(s) would occur.
=' '
span(ng-show="myCalculator().results.sumTradeValue > 0") A total of
=' '
span.text-success {{pair.base.format(myCalculator().results.sumTradeValue)}}
=' '
| would be received immediately for a total cost of
=' '
span.text-danger {{pair.quote.format(myCalculator().results.sumTradeAmount)}}
| .
=' '
span(ng-show="myCalculator().results.remainder > 0") A total of
=' '
span.text-danger {{pair.quote.format(myCalculator().results.remainder)}}
=' '
| would remain on order at price {{pair.base.format(myCalculator().targetPrice)}}.
p(ng-show="myCalculator().results.numTrades > 1") The highest price paid would be {{pair.base.format(myCalculator().results.maxTradeRate)}} and the lowest would be {{pair.base.format(myCalculator().results.minTradeRate)}}
div(ng-show="myCalculator().results.numTrades > 1")
a(href="#", ng-hide="showCalcTrades", ng-click="toggleShowTrades($event, true)") Show Trades
a(href="#", ng-show="showCalcTrades", ng-click="toggleShowTrades($event, false)") Hide Trades
.footer-control(ng-show="showCalcTrades && myCalculator().results.numTrades > 1")
.flex-grid.flex-header
.flex-rate.text-right Rate ({{pair.base.symbol}})
.flex-1.text-right Amount ({{pair.quote.symbol}})
.flex-1.text-right Value ({{pair.base.symbol}})
.flex-grid.flex-body(ng-repeat="order in myCalculator().results.trades")
.flex-rate.text-right.col-rate(ng-bind-html="formatNumHtml(order.rate, pair.base.precision)")
.flex-1.text-right(ng-bind-html="formatNumHtml(order.amount, 3)")
.flex-1.text-right(ng-bind-html="formatNumHtml(order.value, 3)")
.footer-control(ng-show="showSettings")
dl
dt Book Precision ({{model.summaryPrecision}} decimals)
dd
input(type="range",min="1", max="8", ng-model="model.summaryPrecision", ng-model-options="modelOptions")
dl
dt Show Walls ({{model.topWalls}} walls)
dd
input(type="range",min="1", max="20", ng-model="model.topWalls", ng-model-options="modelOptions")
dl
dt Wall Max Price Change ({{model.maxChange}}%)
dd
input(type="range",min="1", max="2000", ng-model="model.maxChange", ng-model-options="modelOptions")
.footer-control.text-right
.btn-group
button.btn.btn-sm(ng-click="showCalculator = !showCalculator", ng-class="{'btn-success': showCalculator, 'btn-info': !showCalculator}")
span.glyphicon.glyphicon-list-alt
=' '
| Calculator
button.btn.btn-sm(ng-click="showSettings = !showSettings", ng-class="{'btn-success': showSettings, 'btn-info': !showSettings}")
span.glyphicon.glyphicon-cog
=' '
| Settings
script(type="text/ng-template", id="/trade-history.tpl")
.panel.panel-default.panel-condensed(ng-class="{frozen: frozen}")
.panel-body.small
.flex-grid.flex-header.flex-head-scrollable
.flex-ago.text-left When
.flex-rate.text-right Rate ({{pair.base.symbol}})
.flex-1.text-right {{pair.quote.symbol}}
.flex-1.text-right {{pair.base.symbol}}
.scrollable-body.trade-history
.flex-grid.flex-body(ng-repeat="trade in model.lines.slice(0,lineCount)", ng-class="{'is-sell text-danger': trade.type === 'sell', 'is-buy text-success': trade.type === 'buy'}")
.flex-ago.text-left(ng-bind="trade.age | ago")
.flex-rate.text-right.col-rate(ng-bind-html="formatNumHtml(trade.rate, pair.base.precision)")
.flex-1.text-right(ng-bind-html="formatNumHtml(trade.amount, pair.quote.precision)")
.flex-1.text-right(ng-bind-html="formatNumHtml(trade.value, pair.base.precision)")
.panel-footer.small.text-mono
.text-right.text-muted.pull-left
div(ng-if="nthTrade(avgFirstNth)") wAVG (< {{nthTradeAgo(avgFirstNth)|ago}}): {{pair.base.format(model.averagePrice(nthTradeDate(avgFirstNth)))}}
div(ng-if="nthTrade(avgSecondNth)") wAVG (< {{nthTradeAgo(avgSecondNth)|ago}}): {{pair.base.format(model.averagePrice(nthTradeDate(avgSecondNth)))}}
div(ng-if="nthTrade(avgThirdNth)") wAVG (< {{nthTradeAgo(avgThirdNth)|ago}}): {{pair.base.format(model.averagePrice(nthTradeDate(avgThirdNth)))}}
.text-right
.text-success BUYS: {{pair.quote.format(model.amountBought, 3)}} / {{pair.base.format(model.totalBought, 3)}} ~ {{pair.base.format(model.totalBought / model.amountBought)}}
.text-danger SELLS: {{pair.quote.format(model.amountSold, 3)}} / {{pair.base.format(model.totalSold, 3)}} ~ {{pair.base.format(model.totalSold / model.amountSold)}}
.clearfix
script(type="text/ng-template", id="/activity-log.tpl")
.panel.panel-default.panel-condensed
.panel-body.small
.flex-grid.flex-header.flex-head-scrollable
.flex-timestamp.text-left Date
.flex-3.text-left Message
.scrollable-body
.flex-grid.flex-body(ng-repeat="event in model.lines", ng-class="event.cls")
.flex-timestamp.text-left(ng-bind="event.date | date:'h:mm:ss a'", style="width:125px")
.flex-3.text-left(ng-bind="event.message")
'use strict';
angular
.module('exchangeApp', [
'ngSanitize',
'ui.bootstrap',
'ngAudio',
'angular-exchange',
'angular-exchange-poloniex'
])
.config(function($locationProvider) {
$locationProvider.html5Mode(false);
$locationProvider.hashPrefix('!');
})
.controller('MainCtrl', function ($location, $scope, $timeout, $changeCalc, $debounce, $aeutil, Currency, CurrencyPair, EAPoloniex, BookLiquidator, UrlWatcher, AlarmSystem, TradeBook) {
var
urlWatch = new UrlWatcher($scope.exchange = new EAPoloniex()),
defaultPrecision = 8,
defaultDepth = 150, // 100% change
defaultPair = UrlWatcher.urlPair() || 'BTC-BTS', //
lastTrade;
$scope.depthChartHeight = 50;
$scope.dumpLastTrades = function (timeframe) {
var
ex = $scope.exchange,
baseFmt = ex.pair.base.format.bind(ex.pair.base),
quoteFmt = ex.pair.quote.format.bind(ex.pair.quote),
targetDate = new Date(Date.now() - timeframe),
match = ex.trades.findTradesNewerThan(targetDate);
console.log('target date:', targetDate.toISOString());
console.log(match.map(function (trade) {
return trade.date.toISOString()
+ ' - ' + baseFmt(trade.rate)
+ ', ' + quoteFmt(trade.amount);
}).join('\n'));
};
$scope.switchPair = function (event, market) {
$scope.alarmSystem.clear(); // stop+clear all alarms when changing coins
event.preventDefault();
$location.path('/').search({ pair: market.format(UrlWatcher.FORMAT_PAIR) });
};
$scope.windowBlink = false;
$scope.alarmSystem = new AlarmSystem($scope.exchange);
$scope.calculatorModelOpts = {
debounce: 500
};
[$scope.exchange.buys, $scope.exchange.sells]
.forEach(function (book) {
book.summaryPrecision = defaultPrecision;
book.maxChange = defaultDepth;
});
$scope.exchange.fetchPairs()
.then(function (pairs) {
urlWatch.push(defaultPair);
});
$scope.obMaxLength = 50;
$scope.calculator = {
sell: new BookLiquidator($scope.exchange.buys),
buy: new BookLiquidator($scope.exchange.sells)
};
$scope.exchange.on($scope, 'sync-start', function () {
$scope.setWindowTitleStat('loading');
});
$scope.exchange.on($scope, 'sync-finish', function () {
var
trades = $scope.exchange.trades.lines,
ltrade = trades[0];
if(ltrade) {
lastTrade = ltrade.rate;
}
else {
lastTrade = undefined;
}
$scope.setWindowTitleRate(lastTrade);
if(lastTrade) {
$scope.alarmSystem.runAlarmCheck(lastTrade);
}
urlWatch.watch();
});
var lchangeBlink;
$scope.changeBlink = function (change) {
if(lchangeBlink) {
$timeout.cancel(lchangeBlink);
}
if($scope.windowBlink) {
$scope.changeBlinkSell = change < 0;
$scope.changeBlinkBuy = change > 0;
}
$scope.setWindowTitleRate(null, change);
lchangeBlink = $timeout(function(){
delete $scope.changeBlinkSell;
delete $scope.changeBlinkBuy;
lchangeBlink = undefined;
$timeout(function(){ $scope.setWindowTitleRate(); }, 200);
}, 400);
};
$scope.lastTradeRate = function () {
if(!$scope.exchange || !$scope.exchange.trades || !$scope.exchange.trades.lines || $scope.exchange.trades.lines.length === 0) {
return false;
}
return $scope.exchange.trades.lines[0].rate;
};
$scope.setWindowTitleStat = function (stat) {
if(!$scope.exchange.pair) return;
document.title = $scope.exchange.pair.quote.symbol + ' ['+stat+']';
};
$scope.setWindowTitleRate = function (rate, change) {
if(!$scope.exchange.pair) return;
rate = rate || $scope.lastTradeRate();
if(!rate) return;
document.title = ($scope.exchange.pair.quote.symbol + ' '+ rate);
if(angular.isNumber(change)) {
var pctChange = Math.abs($aeutil.round(change*100, 2)) + '%';
if(change > 0) {
document.title += ' \u25B2 +' + pctChange;
}
else if(change < 0) {
document.title += ' \u25BC -' + pctChange;
}
}
};
$scope.$watch(function () {
return $scope.exchange.isOnline();
}, function (online) {
if(!online) $scope.setWindowTitleStat('offline');
});
var
recalcSell = $debounce(function (rate, amount, curr, change) {
if(!$scope.calculator || !$scope.calculator.buy) return;
$scope.calculator.buy.recalculate();
}, 500),
recalcBuy = $debounce(function (rate, amount, curr, change) {
if(!$scope.calculator || !$scope.calculator.sell) return;
$scope.calculator.sell.recalculate();
}, 500);
$scope.exchange.on($scope, 'create-order-sell', recalcSell);
$scope.exchange.on($scope, 'remove-order-sell', recalcSell);
$scope.exchange.on($scope, 'create-order-buy', recalcBuy);
$scope.exchange.on($scope, 'remove-order-buy', recalcBuy);
$scope.exchange.on($scope, 'trade', $debounce(function (type, rate, amount, date) {
var tlast = lastTrade, change;
lastTrade = parseFloat(rate);
$scope.setWindowTitleRate(rate);
$scope.changeBlink($changeCalc(tlast, lastTrade));
$scope.alarmSystem.runAlarmCheck(lastTrade);
recalcSell();
recalcBuy();
}, 500));
})
.factory('SelectOptions', function () {
function SelectOptions (items) {
this.items = angular.isArray(items) ? items : [];
}
SelectOptions.prototype.findIndex = function (id) {
var foundIdx = -1;
this.items.every(function (item, index) {
if(item[0] === id) {
foundIdx = index;
}
return (foundIdx === -1);
});
return foundIdx;
};
SelectOptions.prototype.find = function (id) {
return this.items[this.findIndex(id)]||false;
};
SelectOptions.prototype.firstId = function () {
if(this.items.length > 0) return this.items[0][0];
return false;
};
return SelectOptions;
})
.directive('aeAlarmDropdown', function () {
var
soundStorage = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/59627/',
sounds = [
['Et Voila', 'sounds-1046-et-voila.mp3'],
['System Fault', 'sounds-990-system-fault.mp3'],
['Hiccup', 'sounds-1007-hiccup.mp3'],
['Comm. Channel', 'sounds-917-communication-channel.mp3'],
['You Know', 'sounds-900-you-know.mp3'],
['Solemn', 'sounds-882-solemn.mp3'],
['Credulous', 'sounds-889-credulous.mp3'],
['Conclusion', 'sounds-930-conclusion.mp3'],
['Gets in the way', 'sounds-874-gets-in-the-way.mp3'],
['Capisci', 'sounds-946-capisci.mp3'],
['No Trespassing', 'sounds-960-no-trespassing.mp3'],
['Isnt It', 'sounds-969-isnt-it.mp3'],
['Served', 'sounds-913-served.mp3']
];
sounds.sort(function (a, b) {
var
aTitle = a[0],
bTitle = b[0];
if(aTitle > bTitle) return 1;
if(aTitle < bTitle) return -1;
return 0;
});
return {
restrict: 'E',
require: 'ngModel',
templateUrl: '/alarm-dropdown.tpl',
scope: {
model: '=ngModel',
exchange: '=aeExchange'
},
controller: ['$scope', 'SelectOptions', 'ngAudio', function ($scope, SelectOptions, ngAudio) {
$scope.showing = false;
$scope.items = [];
$scope.toggleDropdown = function (showing) {
console.log('toggling dropdown', showing);
};
$scope.audioFiles = new SelectOptions(sounds.map(function (sound, index) {
var id = String(index+1);
return [id, sound[0], soundStorage + sound[1]];
}));
$scope.alarmFunctions = new SelectOptions([
['priceBelow', 'Price Below', [function () {
if($scope.exchange) return $scope.exchange.buys.lines[0].rate;
}]],
['priceAbove', 'Price Above', [function () {
if($scope.exchange) return $scope.exchange.sells.lines[0].rate;
}]],
['changePositive', 'Positive price change', [1.5, 300000]],
['changeNegative', 'Negative price change', [1.5, 300000]],
['changeAbsolute', 'Any price change', [1.5, 300000]]
]);
$scope.audioFileLabel = function (id) {
var af = $scope.audioFiles.find(id);
return !!af ? af[1] : false;
};
$scope.audioFileSource = function (id) {
var af = $scope.audioFiles.find(id);
return !!af ? af[2] : false;
};
$scope.audioFileLoad = function (id) {
var
af = $scope.audioFiles.find(id);
if(!af) {
return false
}
if(!af[3]) { // no cache object, inline load
af[3] = ngAudio.load(af[2]);
}
return af[3];
};
$scope.alarmDefaultArgs = function (id) {
var af = $scope.alarmFunctions.find(id);
if(!af || af.length < 3) return [];
var argConstr = af[2];
if(!angular.isArray(argConstr)) return [];
return argConstr.map(function (arg) {
return angular.isFunction(arg) ? arg() : arg;
});
};
var alarmFnWatcher;
$scope.createNewAlarm = function () {
$scope.temporaryAlarm = {
condition: {
name: $scope.alarmFunctions.firstId()
},
audioFile: $scope.audioFiles.firstId(),
looped: true,
maxDuration: 5000
};
alarmFnWatcher = $scope.$watch('temporaryAlarm.condition.name', function (name) {
if(!name) return;
$scope.temporaryAlarm.condition.args = $scope.alarmDefaultArgs(name);
});
};
$scope.showAlarmPrice = function () {
if(!$scope.temporaryAlarm || !$scope.temporaryAlarm.condition) return false;
return ['priceBelow','priceAbove'].indexOf($scope.temporaryAlarm.condition.name) > -1;
};
$scope.showAlarmChange = function () {
if(!$scope.temporaryAlarm || !$scope.temporaryAlarm.condition) return false;
return ['changePositive','changeNegative','changeAbsolute'].indexOf($scope.temporaryAlarm.condition.name) > -1;
};
$scope.saveNewAlarm = function (form) {
if(form.$invalid) return;
var alarm = $scope.temporaryAlarm;
$scope.model.createAlarm(
$scope.audioFileSource(alarm.audioFile),
!!alarm.looped,
alarm.maxDuration,
alarm.condition
);
$scope.cancelNewAlarm();
};
$scope.cancelNewAlarm = function () {
delete $scope.temporaryAlarm;
if(alarmFnWatcher) alarmFnWatcher();
};
$scope.previewAudio = function () {
var alarm = $scope.temporaryAlarm;
if(!alarm || !alarm.audioPreview || $scope.isPreviewPlaying) {
return false;
}
alarm.audioPreview.play();
};
$scope.$watch('model.hasAlarms', function (has) {
if(!has) { delete $scope.showAlarms; }
});
// auto-load audio file on change
$scope.$watch('temporaryAlarm.audioFile', function (audioFile) {
if(!audioFile) {
if($scope.temporaryAlarm) {
delete $scope.temporaryAlarm.audioPreview;
}
return;
}
$scope.temporaryAlarm.audioPreview = $scope.audioFileLoad(audioFile);
});
// monitor preview progress
$scope.$watch('temporaryAlarm.audioPreview.progress', function (prog) {
if(!angular.isNumber(prog)) {
$scope.isPreviewPlaying = false;
return;
}
$scope.isPreviewPlaying = prog > 0 && prog < 1;
});
}]
};
})
.directive('aeOrderBook', function () {
return {
restrict: 'E',
require: 'ngModel',
templateUrl: '/order-book.tpl',
scope: {
model: '=ngModel',
frozen: '=',
pair: '=',
lineCount: '=',
calculatorSell: '=',
calculatorBuy: '='
},
controller: ['$scope', 'Currency', 'BuyOrderBook', 'SellOrderBook', function ($scope, Currency, BuyOrderBook, SellOrderBook) {
$scope.showCalculator = false;
$scope.showSettings = false;
$scope.showCalcTrades = false;
$scope.modelOptions = {
debounce: 500
};
$scope.toggleShowTrades = function (event, enabled) {
event.preventDefault();
$scope.showCalcTrades = !!enabled;
};
$scope.isBuyBook = function () {
return $scope.model instanceof BuyOrderBook;
};
$scope.isSellBook = function () {
return $scope.model instanceof SellOrderBook;
};
$scope.formatNum = Currency.formatNumber.bind(Currency);
$scope.formatNumHtml = function (amount, prec) {
return Currency.formatNumberHtml(amount, null, null, prec);
};
$scope.myCalculator = function (rate) {
return $scope.isBuyBook() ? $scope.calculatorBuy : $scope.calculatorSell;
};
$scope.otherCalculator = function (rate) {
return $scope.isBuyBook() ? $scope.calculatorSell : $scope.calculatorBuy;
};
$scope.setCalculatorTarget = function (rate) {
var calc = $scope.otherCalculator();
if(calc) {
calc.targetPrice = rate;
}
};
$scope.hasCalculatedResults = function () {
var calc = $scope.myCalculator();
return !!calc && calc.hasResults;
};
}]
};
})
.directive('aeTradeHistory', function () {
return {
restrict: 'E',
require: 'ngModel',
templateUrl: '/trade-history.tpl',
scope: {
model: '=ngModel',
frozen: '=',
pair: '=',
lineCount: '='
},
controller: ['$scope', 'Currency', function ($scope, Currency) {
$scope.formatNum = Currency.formatNumber.bind(Currency);
$scope.formatNumHtml = function (amount, prec) {
return Currency.formatNumberHtml(amount, null, null, prec);
};
$scope.nthTrade = function (seek) {
if(!seek || !$scope.model || !$scope.model.lines.length) {
return false;
}
var
lines = $scope.model.lines,
total = lines.length;
if(total === 0) {
return false;
}
if(seek === 'first') {
return lines[0];
}
else if(seek === 'last') {
return lines[total - 1];
}
else if(seek === 'middle' && total > 2) {
return lines[Math.round(total/2)];
}
else if(angular.isNumber(seek) && seek >= 0) {
return lines[Math.min(seek, total - 1)];
}
return false;
};
$scope.nthTradeDate = function (seek) {
var trade = $scope.nthTrade(seek);
if(!trade) return false;
return trade.date;
};
$scope.nthTradeAgo = function (seek) {
var tradeDate = $scope.nthTradeDate(seek);
if(!tradeDate) return false;
return Date.now() - tradeDate;
};
$scope.avgFirstNth = 5;
$scope.avgSecondNth = 'middle';
$scope.avgThirdNth = 'last';
}]
};
})
.directive('aeEventLog', function () {
return {
restrict: 'E',
require: 'ngModel',
templateUrl: '/activity-log.tpl',
scope: {
model: '=ngModel'
}
};
});
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.2/angular-resource.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.0/ui-bootstrap-tpls.min.js"></script>
<script src="https://autobahn.s3.amazonaws.com/autobahnjs/latest/autobahn.min.jgz"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.3/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.3/angular-sanitize.min.js"></script>
<script src="http://danielstern.github.io/ngAudio/angular.audio.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/59627/angular-exchange.min.js?v=32"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/59627/angular-exchange-poloniex.min.js?v=32"></script>
$sb-width: 18px;
$sb-margin: 5px;
@import url(https://fonts.googleapis.com/css?family=Oswald);
@import url(https://fonts.googleapis.com/css?family=Roboto);
@import url(https://fonts.googleapis.com/css?family=Ubuntu);
@import url(https://fonts.googleapis.com/css?family=Ubuntu+Mono);
@import "bourbon";
body {
font-family: Roboto;
@include transition-property(background-color);
@include transition-duration(150ms);
&.blink-sell {
background-color: #FFEBDE;
}
&.blink-buy {
background-color: #D9F8D8;
}
}
.text-mono {
font-family: "Ubuntu Mono";
}
.text-oswald {
font-family: Oswald;
}
.header-buttons {
line-height: 80px;
}
.last-rates {
label {
margin: 0;
display: block;
}
}
.label-live {
font-size: 10px;
}
.navbar-inverse {
border-radius: 0 0 5px 5px;
.navbar-form {
label {
color: #ccc;
}
}
}
.panel-footer {
.footer-control {
padding: 5px 0 0;
margin: 5px 0;
border-top: 1px solid #ddd;
}
}
.footer-control {
dl {
margin-bottom: 0;
}
}
.flex-grid {
display: flex;
.flex-1 { flex: 1; }
.flex-2 { flex: 2; }
.flex-3 { flex: 3; }
.flex-4 { flex: 4; }
.flex-5 { flex: 5; }
.flex-rate { flex: 0 0 80px; }
.flex-ago { flex: 0 0 140px; }
.flex-timestamp { flex: 0 0 80px; }
&.flex-header {
padding-right: $sb-margin;
font-weight: bold;
font-size: 0.8em;
}
&.flex-head-scrollable {
padding-right: $sb-width + $sb-margin;
}
&.flex-body {
padding-right: $sb-margin;
}
}
.number {
font-family: Ubuntu;
.number-remainder {
opacity: 0.25;
}
}
.scrollable-body {
max-height: 200px;
overflow-y: auto;
overflow-x: hidden;
.flex-grid {
border-top: 1px solid #ddd;
line-height: 15px;
}
}
.text-bold {
font-weight: bold;
}
.col-rate {
background-color: #333;
color: #fff;
padding: 0 5px;
}
.trade-history {
.is-buy {
.col-rate {
background-color: #3C7643;
}
}
.is-sell {
.col-rate {
background-color: #754A3C;
}
}
}
.panel {
&.panel-condensed {
.panel-header,
.panel-footer,
.panel-body {
padding: 3px 5px;
}
}
}
.frozen {
opacity: 0.4;
}
.scrollable-menu {
height: auto;
max-height: 200px;
overflow-x: hidden;
}
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment