From 544b7999fa4a11fe172426b4b3072baca6c17303 Mon Sep 17 00:00:00 2001 From: Zahrun <10415894+Zahrun@users.noreply.github.com> Date: Sun, 11 Dec 2022 22:19:56 +0100 Subject: [PATCH 1/4] feat: support coinlist trades import --- src/parsers/trades/coinlist/index.ts | 58 ++++++++++++++++++++++++++++ src/parsers/trades/index.ts | 2 + src/types/locations.ts | 2 + 3 files changed, 62 insertions(+) create mode 100644 src/parsers/trades/coinlist/index.ts diff --git a/src/parsers/trades/coinlist/index.ts b/src/parsers/trades/coinlist/index.ts new file mode 100644 index 0000000..695a080 --- /dev/null +++ b/src/parsers/trades/coinlist/index.ts @@ -0,0 +1,58 @@ +import { getCSVData } from '../../'; +import { EXCHANGES, IImport, IPartialTrade, ITrade } from '../../../types'; +import { createID } from '../../utils'; + +interface ICoinList { + Date: string; + Description: string; + Asset: string; + Amount: string; + Balance: string; +} + +export default async function processData(importDetails: IImport): Promise { + const data: ICoinList[] = await getCSVData(importDetails.data) as ICoinList[]; + const internalFormat: ITrade[] = []; + if (data.length < 1) { + return internalFormat; + } + let splitTrade = data[0]; + let lineContinuity = 0; + for (const trade of data) { + console.log(trade); + const tradeToAdd: IPartialTrade = { + date : new Date(trade.Date).getTime(), + exchange : EXCHANGES.CoinList, + }; + let descriptionSplit = trade.Description.split(' '); + let type = descriptionSplit[0]; + if (type === 'Sold' || type === 'Bought') { + switch (lineContinuity) { + case 0: { + splitTrade = trade; + lineContinuity = 1; + continue; + } + case 1: { + lineContinuity = 0; + tradeToAdd.boughtCurrency = splitTrade.Asset; + tradeToAdd.soldCurrency = trade.Asset; + tradeToAdd.amountSold = Math.abs(parseFloat(trade.Amount)); + tradeToAdd.rate = Math.abs(parseFloat(trade.Amount) / parseFloat(splitTrade.Amount)); + tradeToAdd.ID = createID(tradeToAdd); + internalFormat.push(tradeToAdd as ITrade); + continue; + } + default: { + console.error(`Error parsing CoinList trade lineContinuity=${lineContinuity}`); + break; + } + } + break; + } else { + console.log(`Ignored CoinList trade of type ${type}`); + continue; + } + } + return internalFormat; +} diff --git a/src/parsers/trades/index.ts b/src/parsers/trades/index.ts index 30b720d..ef59523 100644 --- a/src/parsers/trades/index.ts +++ b/src/parsers/trades/index.ts @@ -2,6 +2,7 @@ import { EXCHANGES, ExchangesTradeHeaders, IImport, ITrade } from '@types'; import * as crypto from 'crypto'; import binanceParser from './binance'; import bittrexParser from './bittrex'; +import coinListParser from './coinlist'; import geminiParser from './gemini'; import krakenParser from './kraken'; import poloniexParser from './poloniex'; @@ -10,6 +11,7 @@ import revolutParser from './revolut'; const parserMapping: {[key in EXCHANGES]: any} = { [EXCHANGES.Binance]: binanceParser, [EXCHANGES.Bittrex]: bittrexParser, + [EXCHANGES.CoinList]: coinListParser, [EXCHANGES.Gemini]: geminiParser, [EXCHANGES.Kraken]: krakenParser, [EXCHANGES.Poloniex]: poloniexParser, diff --git a/src/types/locations.ts b/src/types/locations.ts index c0053d4..2c05bd5 100644 --- a/src/types/locations.ts +++ b/src/types/locations.ts @@ -2,6 +2,7 @@ export type Location = EXCHANGES | string; export enum EXCHANGES { Bittrex = 'BITTREX', + CoinList = 'COINLIST', Gemini= 'GEMINI', Poloniex = 'POLONIEX', Kraken = 'KRAKEN', @@ -15,6 +16,7 @@ export enum IncomeImportTypes { export enum ExchangesTradeHeaders { BITTREX = '07230399aaa8d1f15e88e38bd43a01c5ef1af6c1f9131668d346e196ff090d80', + COINLIST = 'a700f71b8629872a0d8d5320612aedcb53f58cc55937eb146124a14360d991f1', GEMINI = '996edee25db7f3d1dd16c83c164c6cff8c6d0f5d6b3aafe6d1700f2a830f6c9e', POLONIEX = 'd7484d726e014edaa059c0137ac91183a7eaa9ee5d52713aa48bb4104b01afb0', KRAKEN = '85bf27e799cc0a30fe5b201cd6a4724e4a52feb433f41a1e8b046924e3bf8dc5', From e9b2ef88a026e4972ff56cd79ab0273da45012f4 Mon Sep 17 00:00:00 2001 From: Zahrun <10415894+Zahrun@users.noreply.github.com> Date: Sat, 17 Dec 2022 14:56:01 +0100 Subject: [PATCH 2/4] feat: better support coinlist trades import --- src/parsers/trades/coinlist/index.ts | 50 ++++++++++------------------ 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/src/parsers/trades/coinlist/index.ts b/src/parsers/trades/coinlist/index.ts index 695a080..197d3de 100644 --- a/src/parsers/trades/coinlist/index.ts +++ b/src/parsers/trades/coinlist/index.ts @@ -13,45 +13,31 @@ interface ICoinList { export default async function processData(importDetails: IImport): Promise { const data: ICoinList[] = await getCSVData(importDetails.data) as ICoinList[]; const internalFormat: ITrade[] = []; - if (data.length < 1) { - return internalFormat; - } - let splitTrade = data[0]; - let lineContinuity = 0; - for (const trade of data) { - console.log(trade); + for (let i = 0; i < data.length; i++) { + const trade = data[i]; const tradeToAdd: IPartialTrade = { date : new Date(trade.Date).getTime(), exchange : EXCHANGES.CoinList, }; let descriptionSplit = trade.Description.split(' '); let type = descriptionSplit[0]; - if (type === 'Sold' || type === 'Bought') { - switch (lineContinuity) { - case 0: { - splitTrade = trade; - lineContinuity = 1; - continue; - } - case 1: { - lineContinuity = 0; - tradeToAdd.boughtCurrency = splitTrade.Asset; - tradeToAdd.soldCurrency = trade.Asset; - tradeToAdd.amountSold = Math.abs(parseFloat(trade.Amount)); - tradeToAdd.rate = Math.abs(parseFloat(trade.Amount) / parseFloat(splitTrade.Amount)); - tradeToAdd.ID = createID(tradeToAdd); - internalFormat.push(tradeToAdd as ITrade); - continue; - } - default: { - console.error(`Error parsing CoinList trade lineContinuity=${lineContinuity}`); - break; - } + switch (type) { + case 'Sold': + case 'Bought': { + i++; + tradeToAdd.boughtCurrency = trade.Asset; + tradeToAdd.soldCurrency = data[i].Asset; + tradeToAdd.amountSold = Math.abs(parseFloat(data[i].Amount)); + tradeToAdd.rate = Math.abs(parseFloat(data[i].Amount) / parseFloat(trade.Amount)); + tradeToAdd.ID = createID(tradeToAdd); + internalFormat.push(tradeToAdd as ITrade); + continue; + } + // TODO: Withdrawal, Distribution, Deposit, Hold + default: { + console.log(`Ignored CoinList trade of type ${type}`); + break; } - break; - } else { - console.log(`Ignored CoinList trade of type ${type}`); - continue; } } return internalFormat; From c458020b5955c54d6e197fcbc21578fbb3f612bf Mon Sep 17 00:00:00 2001 From: Zahrun <10415894+Zahrun@users.noreply.github.com> Date: Sat, 17 Dec 2022 20:42:26 +0100 Subject: [PATCH 3/4] feat: support coinlist pro trades import --- components/TradesTable/index.tsx | 2 ++ src/parsers/trades/coinlist/index.ts | 49 +++++++++++++++++++++++++++- src/parsers/trades/index.ts | 2 +- src/types/locations.ts | 3 +- src/types/trade.ts | 2 ++ 5 files changed, 55 insertions(+), 3 deletions(-) diff --git a/components/TradesTable/index.tsx b/components/TradesTable/index.tsx index c92de88..d244c28 100644 --- a/components/TradesTable/index.tsx +++ b/components/TradesTable/index.tsx @@ -55,6 +55,7 @@ export class TradesTable extends React.Component [ @@ -66,6 +67,7 @@ export class TradesTable extends React.Component{trade.boughtCurrency}, {(trade.amountSold / trade.rate).toFixed(8)}, {`${trade.transactionFee} ${trade.transactionFeeCurrency}`}, + {`${trade.tradeFee} ${trade.tradeFeeCurrency}`}, , ])} /> diff --git a/src/parsers/trades/coinlist/index.ts b/src/parsers/trades/coinlist/index.ts index 197d3de..eff26a2 100644 --- a/src/parsers/trades/coinlist/index.ts +++ b/src/parsers/trades/coinlist/index.ts @@ -10,7 +10,21 @@ interface ICoinList { Balance: string; } +interface ICoinListPro { + portfolio: string; + type: string; + time: string; + amount: string; + balance: string; + 'amount/balance unit': string; + transaction_id: string +} + export default async function processData(importDetails: IImport): Promise { + if (importDetails.data.split(',')[0] === 'portfolio') { + console.log('Detected CoinListPro') + return processProData(importDetails); + } const data: ICoinList[] = await getCSVData(importDetails.data) as ICoinList[]; const internalFormat: ITrade[] = []; for (let i = 0; i < data.length; i++) { @@ -35,7 +49,40 @@ export default async function processData(importDetails: IImport): Promise { + const data: ICoinListPro[] = await getCSVData(importDetails.data) as ICoinListPro[]; + const internalFormat: ITrade[] = []; + for (let i = 0; i < data.length; i++) { + const trade = data[i]; + const tradeToAdd: IPartialTrade = { + date : new Date(trade.time).getTime(), + exchange : EXCHANGES.CoinList, + }; + switch (trade.type) { + case 'match': { + i++; + tradeToAdd.boughtCurrency = trade.balance; + tradeToAdd.tradeFee = Math.abs(parseFloat(data[i].amount)); + tradeToAdd.tradeFeeCurrency = data[i].balance; + i++; + tradeToAdd.soldCurrency = data[i].balance; + tradeToAdd.amountSold = Math.abs(parseFloat(data[i].amount)); + tradeToAdd.rate = Math.abs(parseFloat(data[i].amount) / parseFloat(trade.amount)); + tradeToAdd.ID = createID(tradeToAdd); + internalFormat.push(tradeToAdd as ITrade); + continue; + } + // TODO: deposit, withdrawal + default: { + console.log(`Ignored ${tradeToAdd.exchange} trade of type ${trade.type}`); break; } } diff --git a/src/parsers/trades/index.ts b/src/parsers/trades/index.ts index ef59523..d7c80fd 100644 --- a/src/parsers/trades/index.ts +++ b/src/parsers/trades/index.ts @@ -26,7 +26,7 @@ export default async function processTradesImport(importDetails: IImport): Promi const headers = importDetails.data.substr(0, importDetails.data.indexOf('\n')); const headersHash = crypto.createHash('sha256').update(headers).digest('hex'); for (const key in ExchangesTradeHeaders) { - if (ExchangesTradeHeaders[key] === headersHash) { + if (ExchangesTradeHeaders[key].split(';').includes(headersHash)) { return processTradesImport({ ...importDetails, location: key, diff --git a/src/types/locations.ts b/src/types/locations.ts index 2c05bd5..c490cc6 100644 --- a/src/types/locations.ts +++ b/src/types/locations.ts @@ -16,7 +16,8 @@ export enum IncomeImportTypes { export enum ExchangesTradeHeaders { BITTREX = '07230399aaa8d1f15e88e38bd43a01c5ef1af6c1f9131668d346e196ff090d80', - COINLIST = 'a700f71b8629872a0d8d5320612aedcb53f58cc55937eb146124a14360d991f1', + COINLIST = 'a700f71b8629872a0d8d5320612aedcb53f58cc55937eb146124a14360d991f1;\ +9eccf556ea42d14253a223980bf0023f6cc0d21320db187f6b9b6bbf59ea8678', GEMINI = '996edee25db7f3d1dd16c83c164c6cff8c6d0f5d6b3aafe6d1700f2a830f6c9e', POLONIEX = 'd7484d726e014edaa059c0137ac91183a7eaa9ee5d52713aa48bb4104b01afb0', KRAKEN = '85bf27e799cc0a30fe5b201cd6a4724e4a52feb433f41a1e8b046924e3bf8dc5', diff --git a/src/types/trade.ts b/src/types/trade.ts index 0773119..1de8b25 100644 --- a/src/types/trade.ts +++ b/src/types/trade.ts @@ -13,6 +13,8 @@ export interface ITrade { ID: string; transactionFee: number; transactionFeeCurrency: string; + tradeFee?: number; + tradeFeeCurrency?: string; } export interface ITradeWithFiatRate extends ITrade { From e988562d3255511bb8e36d159bb52cc7c61f40cc Mon Sep 17 00:00:00 2001 From: Zahrun <10415894+Zahrun@users.noreply.github.com> Date: Sat, 17 Dec 2022 22:14:31 +0100 Subject: [PATCH 4/4] fix: coinlist found as duplicates --- src/parsers/trades/coinlist/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/parsers/trades/coinlist/index.ts b/src/parsers/trades/coinlist/index.ts index eff26a2..8b4760c 100644 --- a/src/parsers/trades/coinlist/index.ts +++ b/src/parsers/trades/coinlist/index.ts @@ -44,6 +44,7 @@ export default async function processData(importDetails: IImport): Promise tradeToAdd.amountSold = Math.abs(parseFloat(data[i].amount)); tradeToAdd.rate = Math.abs(parseFloat(data[i].amount) / parseFloat(trade.amount)); tradeToAdd.ID = createID(tradeToAdd); + tradeToAdd.exchangeID = tradeToAdd.ID; internalFormat.push(tradeToAdd as ITrade); continue; }