Skip to content

Commit c5796f9

Browse files
committed
Merge branch 'release/1.6.1'
2 parents 3757d50 + 313b9cf commit c5796f9

32 files changed

Lines changed: 289 additions & 269 deletions

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
<a name="1.6.1"></a>
2+
## [1.6.1](https://github.com/benmarten/CryptoETF/compare/1.6.0...1.6.1) (2018-01-09)
3+
4+
5+
### Features
6+
7+
* **ui:** colorize output; allow to hide exchanges below certain holding treshold ([94a1ac1](https://github.com/benmarten/CryptoETF/commit/94a1ac1))
8+
9+
10+
111
<a name="1.6.0"></a>
212
# [1.6.0](https://github.com/benmarten/CryptoETF/compare/1.5.1...1.6.0) (2018-01-06)
313

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ The tool expects your settings in settings.json. Take a look at settings.example
4343
- rebalanceDeltaTotalPct: Treshold in percent, that will show a Y in the rebalance column, once rebalancing of total portfolio is recommended.
4444
- rebalanceDeltaPct: Treshold in percent, that will show a Y in the rebalance column, once rebalancing of individual position is recommended.
4545
- minValueBtc: Ignore coins that only have a holdingsvalue under a certain bitcoin value.
46+
- 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.
4647
- 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.
4748
- *outputFile*: Path to a file to forward the output to as json.
4849

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"binance": "^1.1.0",
55
"bitfinex": "^1.0.3",
66
"coinbase": "^2.0.6",
7+
"colors": "^1.1.2",
78
"gdax": "^0.4.2",
89
"kraken-api": "^1.0.0",
910
"node-bittrex-api": "^0.8.1",
@@ -31,5 +32,5 @@
3132
"testLocal": "./node_modules/.bin/nyc mocha --require babel-core/register test/**/*.js test/**/**/*.js",
3233
"test": "NODE_ENV=test npm run testLocal"
3334
},
34-
"version": "1.6.0"
35+
"version": "1.6.1"
3536
}

settings.example.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"rebalanceDeltaPct": 1.0,
5858
"rebalanceDeltaTotalPct": 10.0,
5959
"minValueBtc": 0.0001,
60+
"exchangeMinValueBtc": 0.0001,
6061
"hideMissingCoins": false
6162
},
6263
"outputFile": false

src/PromiseUtils.js

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/Utils.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,12 @@ export default class Utils {
4646
static hasDriftedAboveTreshold(drift, treshold) {
4747
return (Math.abs(drift) * 100 > treshold) ? 'Y' : ''
4848
}
49+
50+
static colorize(string) {
51+
if (string.startsWith('-')) {
52+
return string.red
53+
} else {
54+
return string.green
55+
}
56+
}
4957
}

src/index.js

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,34 @@ import Coin from './model/Coin'
44
import Utils from './Utils'
55
import fs from 'fs'
66
import * as Settings from './Settings'
7+
import Terminal from './model/Terminal'
78

89
async function refreshPortfolio() {
910
return new Promise(async (resolve) => {
1011
try {
1112
Portfolio.clear()
1213
await Coinmarket.init()
13-
let integrations = {}
1414
let promises = []
1515

1616
for (let account in Settings.accounts) {
1717
let name = Utils.capitalize(account)
18+
let Wallet
1819
try {
19-
integrations[name] = require(`./model/integrations/${name}Wallet`)
20+
Wallet = require(`./model/integrations/${name}Wallet`).default
2021
} catch (ignored) {
2122
console.log(`Warning: Integration for Exchange: ${name} not found!`)
2223
continue
2324
}
24-
try {
25-
if (Settings.accounts[account] && Settings.accounts[account].length > 0) {
26-
console.log(`Retrieving ${name} balance...`)
27-
promises.push(integrations[name].default.getBalance().then(coins => Portfolio.addCoins(coins)))
25+
26+
for (let index in Settings.accounts[account]) {
27+
let credentials = Settings.accounts[account][index]
28+
let integration = new Wallet(credentials)
29+
console.log(`Retrieving ${name} balance...`)
30+
try {
31+
promises.push(integration.getBalance().then(coins => Portfolio.addCoins(coins)))
32+
} catch (e) {
33+
console.log(`Error: An error occured while running integration: ${name}, ${e}`)
2834
}
29-
} catch (e) {
30-
console.log(`Error: An error occured while running integration: ${name}, ${e}`)
3135
}
3236
}
3337

@@ -48,14 +52,14 @@ async function refreshPortfolio() {
4852
Portfolio.removeCoin('USDT')
4953

5054
if (Settings.outputFile) {
51-
fs.writeFile(Settings.outputFile, Portfolio.getJson(), 'utf8', function(err) {
55+
fs.writeFile(Settings.outputFile, Portfolio.getPortfolioJson(), 'utf8', function(err) {
5256
if (err) throw err
5357
console.log(`Saved data to ${Settings.outputFile}...`)
5458
})
5559
}
5660

57-
58-
console.log(Portfolio.getOutput())
61+
let portfolio = Portfolio.getPortfolio()
62+
Terminal.printOutput(portfolio)
5963
}
6064
catch
6165
(error) {

src/model/Coin.js

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import Coinmarket from './Coinmarket'
22
import Portfolio from './Portfolio'
33
import * as Settings from './../Settings'
4+
import _ from 'lodash'
45

56
export default class Coin {
67
symbol
78
// noinspection JSCheckFunctionSignatures
89
amount = parseFloat(0)
910
rank
1011
exchanges = {}
11-
exchangesPossible = {}
1212

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

2424
if (exchange) {
25-
if (this.amount > 0) {
26-
this.addExchange(exchange)
27-
} else {
28-
this.addExchangePossible(exchange)
29-
}
25+
this.addExchange(exchange, amount)
3026
}
3127

3228
if (rank) {
@@ -86,40 +82,36 @@ export default class Coin {
8682
/**
8783
* Records on which exchange the coin is traded and we hold it.
8884
* @param exchange The name of the exchange
85+
* @param amount The amount held on the exchange
8986
*/
90-
addExchange(exchange) {
91-
if (typeof exchange === 'object') {
92-
for (let idx in exchange) {
93-
this.exchanges[exchange[idx]] = 1
94-
}
87+
addExchange(exchange, amount) {
88+
if (!exchange || exchange.length === 0) {
89+
return
9590
}
96-
this.exchanges[exchange] = 1
97-
}
98-
99-
/**
100-
* Records on which exchange the coin is traded.
101-
* @param exchange The name of the exchange
102-
*/
103-
addExchangePossible(exchange) {
10491
if (typeof exchange === 'object') {
10592
for (let idx in exchange) {
106-
this.exchangesPossible[exchange[idx]] = 1
93+
this.exchanges[exchange[idx]] = amount
10794
}
10895
}
109-
this.exchangesPossible[exchange] = 1
96+
this.exchanges[exchange] = amount
11097
}
11198

11299
getExchanges() {
113100
return Object.keys(this.exchanges)
114101
}
115102

116103
getExchangesString() {
117-
let result
118-
if (Object.keys(this.exchanges).length > 0) {
119-
result = Object.keys(this.exchanges).toString()
104+
let coin = this
105+
let result = _.pickBy(this.exchanges, (value) => {
106+
return (value * Coinmarket.getBtcForX(coin.symbol)) >
107+
(Settings.options.exchangeMinValueBtc || 0)
108+
})
109+
if (Object.keys(result).length > 0) {
110+
result = Object.keys(result).toString()
120111
} else {
121-
result = Object.keys(this.exchangesPossible).toString()
112+
result = Object.keys(this.exchanges).toString().blue.italic
122113
}
114+
123115
return (result.charAt(result.length - 1) === ',') ? result.slice(0, -1) : result
124116
}
125117
}

src/model/Portfolio.js

Lines changed: 10 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import Utils from './../Utils'
22
import Coinmarket from './Coinmarket'
33
// noinspection NpmUsedModulesInstalled
4-
import {table} from 'table'
5-
import Format from './../Format'
64
import * as Settings from './../Settings'
75

86
let portfolio = {}
@@ -21,11 +19,7 @@ export default class Portfolio {
2119
if (portfolio[coin.getSymbol()]) {
2220
let existingCoin = portfolio[coin.getSymbol()]
2321
existingCoin.addAmount(coin.getAmount())
24-
if (coin.getAmount() > 0) {
25-
existingCoin.addExchange(coin.getExchanges())
26-
} else {
27-
existingCoin.addExchangePossible(coin.getExchanges())
28-
}
22+
existingCoin.addExchange(coin.getExchanges(), coin.getAmount())
2923
} else {
3024
portfolio[coin.getSymbol()] = coin
3125
this.updateHighestRankWithBalance(coin)
@@ -121,123 +115,18 @@ export default class Portfolio {
121115
}
122116

123117
/**
124-
* Formats the output for the command line.
125-
* @return {string} A string with the result.
118+
* Returns the current portfolio.
119+
* @return {{Object}} The portfolio.
126120
*/
127-
static getOutput() {
128-
let stretchFactor = Portfolio.getStretchFactor()
129-
let data = [
130-
['#', 'SYMBOL', 'AMOUNT', 'VALUE', 'VALUE', 'ALLOCATION', 'ALLOCATION', 'TARGET', 'TARGET', 'BUY/SELL', 'BUY/SELL', 'BUY/SELL', 'DRIFT', 'REBALANCE', 'EXCHANGES'],
131-
['', '', '', '฿', '$', 'actual %', 'target %', '฿', '$', '฿', 'ETH', '$', '%', '', '']
132-
]
133-
let sortedKeys = Utils.getSortedKeys(portfolio, 'rank')
134-
let targetSum = []
135-
let targetValueUsd = this.getTargetValueUsd()
136-
let targetValueBtc = this.getTargetValueUsd() / Coinmarket.getBtcUsd()
137-
targetSum['allocationActualPct'] = 0
138-
targetSum['allocationTargetPct'] = 0
139-
targetSum['targetBtc'] = 0
140-
targetSum['targetUsd'] = 0
141-
for (let index in sortedKeys) {
142-
if (!sortedKeys.hasOwnProperty(index)) {
143-
continue
144-
}
145-
let coin = portfolio[sortedKeys[index]]
146-
let allocationActualPct = coin.getRelativeMarketCap()
147-
let allocationTargetPct = coin.getRelativeMarketCapRecommended() / stretchFactor
148-
let targetBtc = coin.getRelativeMarketCapRecommended() / stretchFactor * targetValueBtc
149-
let targetUsd = coin.getRelativeMarketCapRecommended() / stretchFactor * targetValueUsd
150-
let drift = Math.abs((coin.getUsdValue() - targetUsd) / targetValueUsd)
151-
data.push([
152-
coin.getRank(),
153-
coin.getSymbol(),
154-
Utils.round(coin.getAmount(), 2),
155-
Format.bitcoin(coin.getBtcValue()),
156-
Format.money(coin.getUsdValue(), 0),
157-
Format.percent(allocationActualPct),
158-
Format.percent(allocationTargetPct),
159-
Format.bitcoin(targetBtc),
160-
Format.money(targetUsd),
161-
Format.bitcoin(targetBtc - coin.getBtcValue(), 8),
162-
Format.bitcoin((targetBtc - coin.getBtcValue()) / Coinmarket.getBtcEth(), 8),
163-
Format.money(targetUsd - coin.getUsdValue()),
164-
Format.percent(drift),
165-
Utils.hasDriftedAboveTreshold(drift, Settings.options.rebalanceDeltaPct),
166-
coin.getExchangesString()
167-
])
168-
targetSum['allocationActualPct'] += allocationActualPct || 0
169-
targetSum['allocationTargetPct'] += allocationTargetPct || 0
170-
targetSum['targetBtc'] += targetBtc
171-
targetSum['targetUsd'] += targetUsd
172-
}
173-
174-
let drift = (targetSum['targetBtc'] - this.getSumBtc()) / targetSum['targetBtc']
175-
data.push([
176-
'',
177-
'',
178-
'',
179-
Format.bitcoin(this.getSumBtc()),
180-
Format.money(this.getSumUsd()),
181-
Format.percent(targetSum['allocationActualPct']),
182-
Format.percent(targetSum['allocationTargetPct']),
183-
Format.bitcoin(targetSum['targetBtc']),
184-
Format.money(targetSum['targetUsd']),
185-
Format.bitcoin(targetSum['targetBtc'] - this.getSumBtc()),
186-
'',
187-
Format.money(targetSum['targetUsd'] - this.getSumUsd()),
188-
Format.percent(drift),
189-
Utils.hasDriftedAboveTreshold(drift, (Settings.options.rebalanceDeltaTotalPct ||
190-
Settings.options.rebalanceDeltaPct)),
191-
''])
192-
193-
// noinspection JSUnusedGlobalSymbols
194-
let config = {
195-
columns: {
196-
2: {
197-
alignment: 'right'
198-
},
199-
3: {
200-
alignment: 'right'
201-
},
202-
4: {
203-
alignment: 'right'
204-
},
205-
5: {
206-
alignment: 'right'
207-
},
208-
6: {
209-
alignment: 'right'
210-
},
211-
7: {
212-
alignment: 'right'
213-
},
214-
8: {
215-
alignment: 'right'
216-
},
217-
9: {
218-
alignment: 'right'
219-
},
220-
10: {
221-
alignment: 'right'
222-
},
223-
11: {
224-
alignment: 'right'
225-
},
226-
12: {
227-
alignment: 'right'
228-
},
229-
13: {
230-
alignment: 'right'
231-
}
232-
},
233-
drawHorizontalLine: (index, size) => {
234-
return index === 0 || index === 2 || index === size - 1 || index === size
235-
}
236-
}
237-
return table(data, config)
121+
static getPortfolio() {
122+
return portfolio
238123
}
239124

240-
static getJson() {
125+
/**
126+
* Returns the portfolio as json string.
127+
* @return {string} The portfolio as json string.
128+
*/
129+
static getPortfolioJson() {
241130
return JSON.stringify(portfolio, null, 2)
242131
}
243132
}

0 commit comments

Comments
 (0)