Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions components/TradesTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export class TradesTable extends React.Component<ITradeTableProps, {popup: strin
'Bought Currency',
'Amount Bought',
'Transaction Fee',
'Trade Fee',
'',
]}
rows={this.props.trades.map((trade) => [
Expand All @@ -66,6 +67,7 @@ export class TradesTable extends React.Component<ITradeTableProps, {popup: strin
<span>{trade.boughtCurrency}</span>,
<span>{(trade.amountSold / trade.rate).toFixed(8)}</span>,
<span>{`${trade.transactionFee} ${trade.transactionFeeCurrency}`}</span>,
<span>{`${trade.tradeFee} ${trade.tradeFeeCurrency}`}</span>,
<i className='fa fa-pencil-square' onClick={this.changePopupStatus(trade.ID)}/>,
])}
/>
Expand Down
15 changes: 14 additions & 1 deletion pages/import.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ const Import = () => {
const [showNewIncome, setShowNewIncome] = useState(false);
const [processing, setProcessing] = useState(false);
const [fileBrowseOpen, setFileBrowseOpen] = useState(false);
const [secondFileData, setSecondFileData] = useState<string>('');
const [processedData, setProcessedData] = useState<ProcessedDataTypes>([]);
const [duplicateData, setDuplicateData] = useState<DuplicateDataTypes>([]);
const [alertData, setAlertData] = useState<IAlertData | undefined>(undefined);
Expand Down Expand Up @@ -179,6 +180,8 @@ const Import = () => {
setDuplicateData,
setAlertData,
setProcessedData,
secondFileData,
setSecondFileData,
)}
browse={fileBrowseOpen}
/>
Expand Down Expand Up @@ -305,6 +308,8 @@ const readFile = (
setDuplicateData: (duplicateData: DuplicateDataTypes) => void,
setAlertData: (alertData: IAlertData) => void,
setProcessedData: (processedData: ProcessedDataTypes) => void,
secondFileData: string,
setSecondFileData: (secondFileData: string) => void,
) => async (
fileData: string,
input: React.RefObject<HTMLInputElement>
Expand All @@ -318,8 +323,16 @@ const readFile = (
const processedData = await processData({
...importDetails,
data: fileData,
data2: secondFileData,
});
if (processedData && processedData.length) {
if (typeof processedData === 'string') {
setSecondFileData(fileData);
alert(`To import ${processedData} completly, \
please provide both the crypto transactions and the fiat transactions files.\n
Please now select the other one.`);
setFileBrowseOpen(true);
} else if (processedData && processedData.length) {
setSecondFileData('');
const duplicateData = duplicateCheck(
importDetails, savedData, processedData,
);
Expand Down
6 changes: 1 addition & 5 deletions src/parsers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export async function getCSVData(fileData: string): Promise<any> {
});
}

export async function processData(importDetails: IImport): Promise<ITrade[] | ITransaction[] | IIncome[]> {
export async function processData(importDetails: IImport): Promise<ITrade[] | ITransaction[] | IIncome[] | string> {
switch (importDetails.type) {
case ImportType.TRADES:
return await processTradesImport(importDetails);
Expand All @@ -48,7 +48,3 @@ export async function processData(importDetails: IImport): Promise<ITrade[] | I
throw new Error(`Unknown Import Type - ${importDetails.type}`);
}
}




9 changes: 7 additions & 2 deletions src/parsers/trades/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import binanceParser from './binance';
import bittrexParser from './bittrex';
import geminiParser from './gemini';
import krakenParser from './kraken';
import liquidParser from './liquid';
import poloniexParser from './poloniex';
import revolutParser from './revolut';

Expand All @@ -12,19 +13,23 @@ const parserMapping: {[key in EXCHANGES]: any} = {
[EXCHANGES.Bittrex]: bittrexParser,
[EXCHANGES.Gemini]: geminiParser,
[EXCHANGES.Kraken]: krakenParser,
[EXCHANGES.Liquid]: liquidParser,
[EXCHANGES.Poloniex]: poloniexParser,
[EXCHANGES.Revolut]: revolutParser,
}

export default async function processTradesImport(importDetails: IImport): Promise<ITrade[]> {
export default async function processTradesImport(importDetails: IImport): Promise<ITrade[] | string> {
if (importDetails.location in parserMapping) {
const parser = parserMapping[importDetails.location];
return await parser(importDetails);
} else {
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)) {
if (key === EXCHANGES.Liquid && importDetails.data2 === '') {
return 'Liquid trades';
}
return processTradesImport({
...importDetails,
location: key,
Expand Down
224 changes: 224 additions & 0 deletions src/parsers/trades/liquid/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import { getCSVData } from '../../';
import { EXCHANGES, IImport, IPartialTrade, ITrade } from '../../../types';
import { createDateAsUTC, createID } from '../../utils';

enum LiquidOrderDirection {
PAY = 'PAY',
RECEIVE = 'RECEIVE',
}

interface ILiquid {
exchange: string;
currency_type: string;
direction: string;
transaction_id: string;
transaction_type: string;
gross_amount: string;
currency: string;
execution_id: string;
generated_for_type: string;
generated_for_id: string;
transaction_hash: string;
from_address: string;
to_address: string;
state: string;
created_at_jst: string;
updated_at_jst: string;
created_at_utc: string;
updated_at: string;
notes: string;
used_id: string;
account_id: string;
}

interface ILiquidGroup {
[key: string]: ILiquid[];
}

function groupByExecutionID(group: ILiquidGroup, line: ILiquid) {
group[line.execution_id] = group[line.execution_id] ?? [];
group[line.execution_id].push(line);
return group;
}

function groupByCreatedAtUTC(group: ILiquidGroup, line: ILiquid) {
group[line.created_at_utc] = group[line.created_at_utc] ?? [];
group[line.created_at_utc].push(line);
return group;
}

export default async function processData(importDetails: IImport): Promise<ITrade[]> {
let data2: ILiquid[] = [];
if (importDetails.data2 !== undefined ) {
data2 = await getCSVData(importDetails.data2) as ILiquid[];
}
const data: ILiquid[] = (await getCSVData(importDetails.data) as ILiquid[]).concat(data2);
const internalFormat: ITrade[] = [];
const sorted = data.sort(function(a, b){
const dateA = createDateAsUTC(new Date(a.created_at_utc)).getTime();
const dateB = createDateAsUTC(new Date(b.created_at_utc)).getTime();
return dateA - dateB;
});
const grouped = sorted.reduce(groupByExecutionID, {});
for (const execution in grouped) {
const trades = grouped[execution];
const tradeToAdd: IPartialTrade = {
date : createDateAsUTC(new Date(trades[0].created_at_utc)).getTime(),
exchange : EXCHANGES.Liquid,
exchangeID : execution,
};
if (execution === '') {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cant we just do !!execution?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t know that notation. Here I’m testing if the executionID is '', when the cell is empty in the csv

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to replace the condition with !!execution, but the result is not the same, it ignores every line

const dateGrouped = trades.reduce(groupByCreatedAtUTC, {});
for (const date in dateGrouped) {
const groupedTrades = dateGrouped[date];
const firstLine = groupedTrades[0];
switch (firstLine.transaction_type) {
/*case 'funding': {
// TODO: create a deposit transaction
break;
}
case 'withdrawal': {
// TODO: create a withdrawal transaction
break;
}*/
case 'quick_exchange': {
if (groupedTrades.length == 2) {
internalFormat.push(addTrade(tradeToAdd, groupedTrades[0], groupedTrades[1]));
} else {
console.error(`Error parsing ${tradeToAdd.exchange} quick exchange.
It extends over ${groupedTrades.length} lines`);
}
break;
}
default: {
console.log(`Ignored ${tradeToAdd.exchange} trade of type ${firstLine.transaction_type}`);
}
}
}
continue;
}
switch (trades[0].transaction_type) {
case 'trade': {
switch (trades.length) {
case 1: {
internalFormat.push(addSingleLineTrade(tradeToAdd, trades[0]));
break;
}
case 2: {
internalFormat.push(addTrade(tradeToAdd, trades[0], trades[1]));
break;
}
case 3: {
internalFormat.push(addTrade(tradeToAdd, trades[0], trades[1], trades[2]));
break;
}
case 4: {
internalFormat.push(addTrade(tradeToAdd, trades[0], trades[1], trades[2], trades[3]));
break;
}
case 6: {
let secondTrade = tradeToAdd;
if (trades[0].direction !== trades[2].direction) {
internalFormat.push(addTrade(tradeToAdd, trades[0], trades[2], trades[4], trades[5]));
internalFormat.push(addTrade(secondTrade, trades[1], trades[3]));
} else {
internalFormat.push(addTrade(tradeToAdd, trades[0], trades[3], trades[4], trades[5]));
internalFormat.push(addTrade(secondTrade, trades[1], trades[2]));
}
break;
}
case 8: {
let secondTrade = tradeToAdd;
if (trades[0].direction !== trades[2].direction) {
internalFormat.push(addTrade(tradeToAdd, trades[0], trades[2], trades[4], trades[5]));
internalFormat.push(addTrade(secondTrade, trades[1], trades[3], trades[6], trades[7]));
} else {
internalFormat.push(addTrade(tradeToAdd, trades[0], trades[3], trades[4], trades[5]));
internalFormat.push(addTrade(secondTrade, trades[1], trades[2], trades[6], trades[7]));
}
break;
}
default: {
console.error(`Error parsing ${tradeToAdd.exchange} trade.
It extends over ${trades.length} lines`);
console.info(trades);
}
}
break;
}
case 'rebate_trade_fee':
case 'trade_fee': {
console.error(`Error parsing ${tradeToAdd.exchange} trade.
First line should not be of type ${trades[0].transaction_type}`);
break;
}
default: {
console.log(`Ignored ${tradeToAdd.exchange} trade of type ${trades[0].transaction_type}`);
}
}
}
return internalFormat;
}

function addSingleLineTrade(
tradeToAdd: IPartialTrade,
trade: ILiquid,
) : ITrade {
if (trade.direction.toUpperCase() === LiquidOrderDirection.PAY) {
tradeToAdd.boughtCurrency = trade.currency;
tradeToAdd.soldCurrency = trade.currency;
tradeToAdd.amountSold = 0;
tradeToAdd.rate = 1;
tradeToAdd.tradeFee = parseFloat(trade.gross_amount);
tradeToAdd.tradeFeeCurrency = trade.currency;
} else if (trade.direction.toUpperCase() === LiquidOrderDirection.RECEIVE) {
// TODO: Replace by an income
tradeToAdd.soldCurrency = 'USDT';
tradeToAdd.boughtCurrency = trade.currency;
tradeToAdd.amountSold = 0;
tradeToAdd.rate = 0 / parseFloat(trade.gross_amount);
}
else {
console.info(trade);
throw new Error(`Error parsing ${tradeToAdd.exchange} trade.direction=${trade.direction}`);
}
tradeToAdd.ID = createID(tradeToAdd);
return tradeToAdd as ITrade;
}

function addTrade(
tradeToAdd: IPartialTrade,
firstHalf: ILiquid,
secondHalf: ILiquid,
feeTrade?: ILiquid,
rebateFeeTrade?: ILiquid,
): ITrade {
let firstHalfDirection = firstHalf.direction.toUpperCase();
let secondHalfDirection = secondHalf.direction.toUpperCase();
if (firstHalfDirection === LiquidOrderDirection.PAY && secondHalfDirection === LiquidOrderDirection.RECEIVE) {
tradeToAdd.boughtCurrency = secondHalf.currency;
tradeToAdd.soldCurrency = firstHalf.currency;
tradeToAdd.amountSold = Math.abs(parseFloat(firstHalf.gross_amount));
tradeToAdd.rate = Math.abs(parseFloat(firstHalf.gross_amount) / parseFloat(secondHalf.gross_amount));
} else if (firstHalfDirection === LiquidOrderDirection.RECEIVE && secondHalfDirection === LiquidOrderDirection.PAY) {
tradeToAdd.soldCurrency = secondHalf.currency;
tradeToAdd.boughtCurrency = firstHalf.currency;
tradeToAdd.amountSold = Math.abs(parseFloat(secondHalf.gross_amount));
tradeToAdd.rate = Math.abs(parseFloat(secondHalf.gross_amount) / parseFloat(firstHalf.gross_amount));
} else {
console.info(firstHalf);
console.info(secondHalf);
throw new Error(`Error parsing ${tradeToAdd.exchange} firstHalf.direction=${firstHalf.direction}
and secondHalf.direction=${secondHalf.direction}`);
}
if (feeTrade !== undefined) {
let amount = parseFloat(feeTrade.gross_amount);
if (rebateFeeTrade !== undefined) {
amount -= parseFloat(rebateFeeTrade.gross_amount);
}
tradeToAdd.tradeFee = amount;
tradeToAdd.tradeFeeCurrency = feeTrade.currency;
}
tradeToAdd.ID = createID(tradeToAdd);
return tradeToAdd as ITrade;
}
1 change: 1 addition & 0 deletions src/types/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface IImport {
currency?: string;
location: Location;
data: string;
data2?: string;
}

export interface IDuplicate {
Expand Down
3 changes: 3 additions & 0 deletions src/types/locations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export type Location = EXCHANGES | string;
export enum EXCHANGES {
Bittrex = 'BITTREX',
Gemini= 'GEMINI',
Liquid = 'LIQUID',
Poloniex = 'POLONIEX',
Kraken = 'KRAKEN',
Binance = 'BINANCE',
Expand All @@ -16,6 +17,8 @@ export enum IncomeImportTypes {
export enum ExchangesTradeHeaders {
BITTREX = '07230399aaa8d1f15e88e38bd43a01c5ef1af6c1f9131668d346e196ff090d80',
GEMINI = '996edee25db7f3d1dd16c83c164c6cff8c6d0f5d6b3aafe6d1700f2a830f6c9e',
LIQUID = '12c125c9080e41e087ff0955826bba94568d637214b166a296447e53ae469abe;\
271db9c7cabdb85db30b433b24a8e446640f1a9c0195b7120bf5275527823c72',
POLONIEX = 'd7484d726e014edaa059c0137ac91183a7eaa9ee5d52713aa48bb4104b01afb0',
KRAKEN = '85bf27e799cc0a30fe5b201cd6a4724e4a52feb433f41a1e8b046924e3bf8dc5',
BINANCE = '4d0d5df894fe488872e513f6148dfa14ff29272e759b7fb3c86d264687a7cf99',
Expand Down
2 changes: 2 additions & 0 deletions src/types/trade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export interface ITrade {
ID: string;
transactionFee: number;
transactionFeeCurrency: string;
tradeFee?: number;
tradeFeeCurrency?: string;
}

export interface ITradeWithFiatRate extends ITrade {
Expand Down