Skip to content

Commit

Permalink
feat(ui): colorize output; allow to hide exchanges below certain hold…
Browse files Browse the repository at this point in the history
…ing treshold
  • Loading branch information
benmarten committed Jan 9, 2018
1 parent 3757d50 commit 94a1ac1
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 150 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ The tool expects your settings in settings.json. Take a look at settings.example
- rebalanceDeltaTotalPct: Treshold in percent, that will show a Y in the rebalance column, once rebalancing of total portfolio is recommended.
- rebalanceDeltaPct: Treshold in percent, that will show a Y in the rebalance column, once rebalancing of individual position is recommended.
- minValueBtc: Ignore coins that only have a holdingsvalue under a certain bitcoin value.
- exchangeMinValueBtc: Don't list exchanges in the exchanges column, with less than the specified BTC value. The complete holding value will still be added in the total sum.
- hideMissingCoins: By default CryptoETF will add all missing coins up to your last coin holding by rank of the coin (global market cap). This option disables that behaviour.
- *outputFile*: Path to a file to forward the output to as json.

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"binance": "^1.1.0",
"bitfinex": "^1.0.3",
"coinbase": "^2.0.6",
"colors": "^1.1.2",
"gdax": "^0.4.2",
"kraken-api": "^1.0.0",
"node-bittrex-api": "^0.8.1",
Expand Down
1 change: 1 addition & 0 deletions settings.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"rebalanceDeltaPct": 1.0,
"rebalanceDeltaTotalPct": 10.0,
"minValueBtc": 0.0001,
"exchangeMinValueBtc": 0.0001,
"hideMissingCoins": false
},
"outputFile": false
Expand Down
10 changes: 10 additions & 0 deletions src/Utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import colors from 'colors';

export default class Utils {
static pad(width, string, padding) {
return (width <= string.length) ? string : this.pad(width, padding + string, padding)
Expand Down Expand Up @@ -46,4 +48,12 @@ export default class Utils {
static hasDriftedAboveTreshold(drift, treshold) {
return (Math.abs(drift) * 100 > treshold) ? 'Y' : ''
}

static colorize(string) {
if (string.startsWith('-')) {
return string.red
} else {
return string.green
}
}
}
7 changes: 4 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Coin from './model/Coin'
import Utils from './Utils'
import fs from 'fs'
import * as Settings from './Settings'
import Terminal from './model/Terminal'

async function refreshPortfolio() {
return new Promise(async (resolve) => {
Expand Down Expand Up @@ -48,14 +49,14 @@ async function refreshPortfolio() {
Portfolio.removeCoin('USDT')

if (Settings.outputFile) {
fs.writeFile(Settings.outputFile, Portfolio.getJson(), 'utf8', function(err) {
fs.writeFile(Settings.outputFile, Portfolio.getPortfolioJson(), 'utf8', function(err) {
if (err) throw err
console.log(`Saved data to ${Settings.outputFile}...`)
})
}


console.log(Portfolio.getOutput())
let portfolio = Portfolio.getPortfolio()
Terminal.printOutput(portfolio)
}
catch
(error) {
Expand Down
42 changes: 17 additions & 25 deletions src/model/Coin.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import Coinmarket from './Coinmarket'
import Portfolio from './Portfolio'
import * as Settings from './../Settings'
import _ from 'lodash'

export default class Coin {
symbol
// noinspection JSCheckFunctionSignatures
amount = parseFloat(0)
rank
exchanges = {}
exchangesPossible = {}

constructor(symbol, amount, exchange, rank) {
if (!symbol || (!amount && amount !== 0)) {
Expand All @@ -22,11 +22,7 @@ export default class Coin {
this.amount = parseFloat(amount)

if (exchange) {
if (this.amount > 0) {
this.addExchange(exchange)
} else {
this.addExchangePossible(exchange)
}
this.addExchange(exchange, amount)
}

if (rank) {
Expand Down Expand Up @@ -86,40 +82,36 @@ export default class Coin {
/**
* Records on which exchange the coin is traded and we hold it.
* @param exchange The name of the exchange
* @param amount The amount held on the exchange
*/
addExchange(exchange) {
if (typeof exchange === 'object') {
for (let idx in exchange) {
this.exchanges[exchange[idx]] = 1
}
addExchange(exchange, amount) {
if (!exchange || exchange.length === 0) {
return
}
this.exchanges[exchange] = 1
}

/**
* Records on which exchange the coin is traded.
* @param exchange The name of the exchange
*/
addExchangePossible(exchange) {
if (typeof exchange === 'object') {
for (let idx in exchange) {
this.exchangesPossible[exchange[idx]] = 1
this.exchanges[exchange[idx]] = amount
}
}
this.exchangesPossible[exchange] = 1
this.exchanges[exchange] = amount
}

getExchanges() {
return Object.keys(this.exchanges)
}

getExchangesString() {
let result
if (Object.keys(this.exchanges).length > 0) {
result = Object.keys(this.exchanges).toString()
let coin = this
let result = _.pickBy(this.exchanges, (value) => {
return (value * Coinmarket.getBtcForX(coin.symbol)) >
(Settings.options.exchangeMinValueBtc || 0)
})
if (Object.keys(result).length > 0) {
result = Object.keys(result).toString()
} else {
result = Object.keys(this.exchangesPossible).toString()
result = Object.keys(this.exchanges).toString().blue.italic
}

return (result.charAt(result.length - 1) === ',') ? result.slice(0, -1) : result
}
}
131 changes: 10 additions & 121 deletions src/model/Portfolio.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import Utils from './../Utils'
import Coinmarket from './Coinmarket'
// noinspection NpmUsedModulesInstalled
import {table} from 'table'
import Format from './../Format'
import * as Settings from './../Settings'

let portfolio = {}
Expand All @@ -21,11 +19,7 @@ export default class Portfolio {
if (portfolio[coin.getSymbol()]) {
let existingCoin = portfolio[coin.getSymbol()]
existingCoin.addAmount(coin.getAmount())
if (coin.getAmount() > 0) {
existingCoin.addExchange(coin.getExchanges())
} else {
existingCoin.addExchangePossible(coin.getExchanges())
}
existingCoin.addExchange(coin.getExchanges(), coin.getAmount())
} else {
portfolio[coin.getSymbol()] = coin
this.updateHighestRankWithBalance(coin)
Expand Down Expand Up @@ -121,123 +115,18 @@ export default class Portfolio {
}

/**
* Formats the output for the command line.
* @return {string} A string with the result.
* Returns the current portfolio.
* @return {{Object}} The portfolio.
*/
static getOutput() {
let stretchFactor = Portfolio.getStretchFactor()
let data = [
['#', 'SYMBOL', 'AMOUNT', 'VALUE', 'VALUE', 'ALLOCATION', 'ALLOCATION', 'TARGET', 'TARGET', 'BUY/SELL', 'BUY/SELL', 'BUY/SELL', 'DRIFT', 'REBALANCE', 'EXCHANGES'],
['', '', '', '฿', '$', 'actual %', 'target %', '฿', '$', '฿', 'ETH', '$', '%', '', '']
]
let sortedKeys = Utils.getSortedKeys(portfolio, 'rank')
let targetSum = []
let targetValueUsd = this.getTargetValueUsd()
let targetValueBtc = this.getTargetValueUsd() / Coinmarket.getBtcUsd()
targetSum['allocationActualPct'] = 0
targetSum['allocationTargetPct'] = 0
targetSum['targetBtc'] = 0
targetSum['targetUsd'] = 0
for (let index in sortedKeys) {
if (!sortedKeys.hasOwnProperty(index)) {
continue
}
let coin = portfolio[sortedKeys[index]]
let allocationActualPct = coin.getRelativeMarketCap()
let allocationTargetPct = coin.getRelativeMarketCapRecommended() / stretchFactor
let targetBtc = coin.getRelativeMarketCapRecommended() / stretchFactor * targetValueBtc
let targetUsd = coin.getRelativeMarketCapRecommended() / stretchFactor * targetValueUsd
let drift = Math.abs((coin.getUsdValue() - targetUsd) / targetValueUsd)
data.push([
coin.getRank(),
coin.getSymbol(),
Utils.round(coin.getAmount(), 2),
Format.bitcoin(coin.getBtcValue()),
Format.money(coin.getUsdValue(), 0),
Format.percent(allocationActualPct),
Format.percent(allocationTargetPct),
Format.bitcoin(targetBtc),
Format.money(targetUsd),
Format.bitcoin(targetBtc - coin.getBtcValue(), 8),
Format.bitcoin((targetBtc - coin.getBtcValue()) / Coinmarket.getBtcEth(), 8),
Format.money(targetUsd - coin.getUsdValue()),
Format.percent(drift),
Utils.hasDriftedAboveTreshold(drift, Settings.options.rebalanceDeltaPct),
coin.getExchangesString()
])
targetSum['allocationActualPct'] += allocationActualPct || 0
targetSum['allocationTargetPct'] += allocationTargetPct || 0
targetSum['targetBtc'] += targetBtc
targetSum['targetUsd'] += targetUsd
}

let drift = (targetSum['targetBtc'] - this.getSumBtc()) / targetSum['targetBtc']
data.push([
'',
'',
'',
Format.bitcoin(this.getSumBtc()),
Format.money(this.getSumUsd()),
Format.percent(targetSum['allocationActualPct']),
Format.percent(targetSum['allocationTargetPct']),
Format.bitcoin(targetSum['targetBtc']),
Format.money(targetSum['targetUsd']),
Format.bitcoin(targetSum['targetBtc'] - this.getSumBtc()),
'',
Format.money(targetSum['targetUsd'] - this.getSumUsd()),
Format.percent(drift),
Utils.hasDriftedAboveTreshold(drift, (Settings.options.rebalanceDeltaTotalPct ||
Settings.options.rebalanceDeltaPct)),
''])

// noinspection JSUnusedGlobalSymbols
let config = {
columns: {
2: {
alignment: 'right'
},
3: {
alignment: 'right'
},
4: {
alignment: 'right'
},
5: {
alignment: 'right'
},
6: {
alignment: 'right'
},
7: {
alignment: 'right'
},
8: {
alignment: 'right'
},
9: {
alignment: 'right'
},
10: {
alignment: 'right'
},
11: {
alignment: 'right'
},
12: {
alignment: 'right'
},
13: {
alignment: 'right'
}
},
drawHorizontalLine: (index, size) => {
return index === 0 || index === 2 || index === size - 1 || index === size
}
}
return table(data, config)
static getPortfolio() {
return portfolio
}

static getJson() {
/**
* Returns the portfolio as json string.
* @return {string} The portfolio as json string.
*/
static getPortfolioJson() {
return JSON.stringify(portfolio, null, 2)
}
}

0 comments on commit 94a1ac1

Please sign in to comment.