Work in progress as time permits.
Created
March 1, 2016 18:14
-
-
Save Stoner19/cd7703810ce32b25056f to your computer and use it in GitHub Desktop.
Crypto-Exchange Visualizer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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' | |
} | |
}; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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