Select Page

Don’t you wish you can view your Binance crypto assets in a Portfolio view, so you can easily see your profit or loss, no matter how painfully red it is? Note: I purposely greyed out some columns for my privacy 🙂

You don’t need to calculate your Binance earnings (or loss) manually now. This tutorial will help you achieve that goal.

First, the 7-step tutorial on How to create a Spring Boot application using Eclipse will be the starting point.

Step 1. Using a web browser, login to your Binance account. From the top menu, select Orders –> Order History. You may also use this URL https://www.binance.us/en/usercenter/history/trade-order


Step 2. Click Export Recent Order History link. Select Custom (max range 3 months). Click OK. The file will automatically downloaded to your default download directory, with a filename Order_History.xlsx.

Step 3. Open the downloaded file in Step 2 using Excel. Save the file as CSV. The CSV file will be needed in Step 7.

Step 4. Design the JSON structure for the portfolio. This is what I come up with.

{
  "appResponse": {
    "portfolio": {
      "title": "SUMMARY (As of Sat Jul 17 08:07:15 EDT 2021)",
      "summary": {
        "asset": [
          {
            "crypto": "ADA",
            "currentPrice": 1.158,
            "avgPrice": 1.3881783105022831,
            "units": 876.01,
            "cost": 1216.0442,
            "profileAllocationPercentage": 18.660009009265043,
            "pnlPercent": -16.58132163288144,
            "pnl": -2016.3620000000005,
            "valueDollar": 1014.4079999999999
          }
        ]
      },
      "details": {
        "title": "Detailed Transactions",
        "transaction": [
          {
            "tradingPrice": 1.3,
            "filled": 10.01,
            "total": 13.01,
            "fee": 0.01,
            "pair": "ADAUSD",
            "date": "2021-03-18 17:18:23",
            "orderType": "BUY",
            "balanceUnits": 10.01
          }
        ]
      }
    }
  }
}

Step 5. From the sample JSON above, create the Plain Old Java Object (POJO) Java classes. You may use this website to generate the initial files. Click Zip as Files to download all Java class files. Extract the files from the zip file. This will be needed in Step 6.

Step 6. Open the Spring Tool Suite (Eclipse) workspace created from the previous tutorial. Create a package named domain under com.teckiejackieblogs.binanceportfolio. Move the zip files extracted from Step 5. Make the necessary adjustments to the initial files:

  • Added a @JsonIgnoreProperties annotation before the class declaration
  • Changed member variables’ visibility from public to private
  • Added getter and setter methods
  • Added toString method

Step 7. Create the REST Controller Java class file, call it BinancePortfolioController.java. The logic for the program will be:

  • For the Detailed Transactions, create a Map of crypto pairs in alphabetical order (TreeMap)
  • For each crypto pair, create a TreeSet of transactions ordered from oldest to newest.
  • Open Order_History.csv
  • Read each row, but skipping the Canceled orders. Populate the Map of crypto pairs and transactions
  • To generate the Summary:
    • Retrieve the current price from a Binance API
    • For each crypto pair, iterate through the transactions. Calculate the average price, units and cost. If the order type is Sell, remove the filled units from the balance units and re-calculate the average price and cost.
    • Compute for the P/L percentage based on the average price and current price where:
      • (1 – (CURRENT_PRICE / AVERAGE_PRICE) ) * -100
    • Compute for P/L $, Portfolio Allocation and Value $
  • Return the JSON object created from building the Summary and Detailed Transactions.
package com.techiejackieblogs.binanceportfolio;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import com.techiejackieblogs.binanceportfolio.domain.AppResponse;
import com.techiejackieblogs.binanceportfolio.domain.Asset;
import com.techiejackieblogs.binanceportfolio.domain.Details;
import com.techiejackieblogs.binanceportfolio.domain.Portfolio;
import com.techiejackieblogs.binanceportfolio.domain.Summary;
import com.techiejackieblogs.binanceportfolio.domain.SymbolPrice;
import com.techiejackieblogs.binanceportfolio.domain.Transaction;

@RestController
public class BinancePortfolioController {
	private static final String FILE_PATH = "C:\\workspaces\\BinancePortfolio\\Order_History.csv";
	private static final String FILE_DELIMITER = ",";
	private static final String BINANCE_PRICE_URL = "https://api.binance.com/api/v3/ticker/price?symbol=";
	private static final String CANCELED_STATUS = "Canceled";
	private static final String ORDER_TYPE_BUY = "BUY";
	private static final String ORDER_TYPE_SELL = "SELL";

	private int HEADER_IDX_DATE = 0;
	private int HEADER_IDX_PAIR = 1;
	private int HEADER_IDX_ORDERTYPE = 2;
	private int HEADER_IDX_AMOUNT = 4;
	private int HEADER_IDX_AVG_TRADING_PRICE = 5;
	private int HEADER_IDX_FILLED = 6;
	private int HEADER_IDX_TOTAL_TRANS_PRICE = 7;
	private int HEADER_IDX_STATUS = 8;

	@RequestMapping("/getPortfolio")
	public AppResponse index() throws Exception {
		AppResponse appResponse = new AppResponse();
		Portfolio portfolio = new Portfolio();
		Summary summary = new Summary();
		Details details = new Details();
		portfolio.setTitle("SUMMARY (As of " + new java.util.Date() + ")");
		RestTemplate restTemplate = new RestTemplate();
		String line, date, pair, orderType = "";
		double price, filled, totalTransPrice, amount, avgTradingPrice = 0;
		TreeMap<String, TreeSet<Transaction>> detailedMap = new TreeMap<String, TreeSet<Transaction>>();
		TreeMap<String, Transaction> summaryMap = new TreeMap<String, Transaction>();
		Transaction transaction = null;
		String[] cell = null;
		TreeSet<Transaction> set = new TreeSet<Transaction>();

		// Parse the CSV file into BufferedReader class constructor
		BufferedReader br = new BufferedReader(new FileReader(FILE_PATH));
		br.readLine();

		// Populate the detailedMap reading only the CSV rows with "Filled" status
		while ((line = br.readLine()) != null) {
			cell = line.split(FILE_DELIMITER); // use comma as separator
			if (cell.length == 9 && !cell[HEADER_IDX_STATUS].equalsIgnoreCase(CANCELED_STATUS)) {

				date = cell[HEADER_IDX_DATE];
				pair = cell[HEADER_IDX_PAIR];
				orderType = cell[HEADER_IDX_ORDERTYPE];
				// Use AvgTradingPrice because Order Price column sometimes show 0
				price = Double.parseDouble(cell[HEADER_IDX_AVG_TRADING_PRICE]);
				amount = Double.parseDouble(cell[HEADER_IDX_AMOUNT]);
				avgTradingPrice = Double.parseDouble(cell[HEADER_IDX_AVG_TRADING_PRICE]);
				filled = Double.parseDouble(cell[HEADER_IDX_FILLED]);
				totalTransPrice = Double.parseDouble(cell[HEADER_IDX_TOTAL_TRANS_PRICE]);

				transaction = new Transaction();
				transaction.setPair(pair);
				transaction.setDate(date);
				transaction.setPair(pair);
				transaction.setOrderType(orderType);
				transaction.setTradingPrice(price);
				transaction.setFilled(filled);
				transaction.setTotal(totalTransPrice);

				set = new TreeSet<Transaction>();
				if (detailedMap.get(pair) != null) {
					set = detailedMap.get(pair);
				}
				set.add(transaction);
				detailedMap.put(pair, set);
			}
		}

		String key = "";
		TreeSet<Transaction> value = null;
		double summaryTradingPrice, summaryFilled, summaryTotal, units, balanceUnits, totalBoughtUnits, totalSoldUnits, ctrSoldUnits, overallTotal = 0;
		Transaction summaryTransaction = new Transaction();
		boolean isDone = false;
		Iterator<Transaction> it = null;
		List<Transaction> transactionList = new ArrayList<Transaction>();
		// Iterate through the detailedMap to populate the summaryMap
		for (Map.Entry<String, TreeSet<Transaction>> entry : detailedMap.entrySet()) {
			key = entry.getKey();
			value = entry.getValue();
			summaryTradingPrice = 0;
			summaryFilled = 0;
			summaryTotal = 0;
			balanceUnits = 0;
			totalBoughtUnits = 0;
			totalSoldUnits = 0;
			for (Transaction transactEntry : value) {
				if (transactEntry.getOrderType().equals(ORDER_TYPE_BUY)) {
					balanceUnits += transactEntry.getFilled();
					totalBoughtUnits += transactEntry.getFilled();
					summaryTradingPrice += transactEntry.getTradingPrice();
					summaryFilled += transactEntry.getFilled();
					summaryTotal += transactEntry.getTotal();
					overallTotal += transactEntry.getTotal();
				} else if (transactEntry.getOrderType().equals(ORDER_TYPE_SELL)) {
					balanceUnits -= transactEntry.getFilled();
					totalSoldUnits += transactEntry.getFilled();
				}
				// Balance Units
				transactEntry.setBalanceUnits(balanceUnits);

				summaryTransaction = new Transaction();
				summaryTransaction.setTradingPrice(summaryTotal / summaryFilled);
				summaryTransaction.setFilled(summaryFilled);
				summaryTransaction.setTotal(summaryTotal);
				summaryMap.put(key, summaryTransaction);
				transactionList.add(transactEntry);
			}

			details.setTitle("Detailed Transactions");
			details.setTransactionList(transactionList);
			ctrSoldUnits = 0;

			if (totalSoldUnits > 0) {
				summaryTradingPrice = 0;
				summaryTotal = 0;
				isDone = false;
				it = value.iterator();
				do {
					transaction = null;
					while (it.hasNext() && !isDone) {
						transaction = it.next();
						if (transaction.getOrderType().equals(ORDER_TYPE_BUY)) {
							units = transaction.getFilled();
							ctrSoldUnits = totalSoldUnits - units;
							if (ctrSoldUnits >= 0) {
								totalSoldUnits = totalSoldUnits - units;
								isDone = false;
							} else {
								isDone = true;
								transaction.setFilled(units - totalSoldUnits);
								transaction.setTotal(transaction.getTradingPrice() * units);
							}
						}
					}
				} while (!isDone);

				while (it.hasNext()) {
					transaction = it.next();
					if (transaction.getOrderType().equals(ORDER_TYPE_BUY)) {
						summaryTradingPrice += transaction.getTradingPrice();
						summaryTotal += transaction.getTotal();
					}
					summaryFilled = balanceUnits;
				}

				summaryTransaction = new Transaction();
				summaryTransaction.setTradingPrice(summaryTotal / summaryFilled);
				summaryTransaction.setFilled(summaryFilled);
				summaryTransaction.setTotal(summaryTotal);
				summaryMap.put(key, summaryTransaction);
			}
		}

		SymbolPrice quote = null;

		double pnl, avgPrice, cost, totalPLDollar = 0, valueDollar, totalValueDollar = 0, currentPrice = 0;
		List<Asset> assetList = new ArrayList<Asset>();
		Asset asset = null;
		// Create the HTML table for the Portfolio from the summaryMap
		for (Map.Entry<String, Transaction> entry : summaryMap.entrySet()) {
			key = entry.getKey();
			transaction = entry.getValue();
			asset = new Asset();
			quote = restTemplate.getForObject(BINANCE_PRICE_URL + key + "T", SymbolPrice.class);
			currentPrice = Double.parseDouble(quote.getPrice());
			avgPrice = transaction.getTradingPrice();
			units = transaction.getFilled();
			cost = transaction.getTotal();
			pnl = (1 - (currentPrice / avgPrice)) * -100;
			// Crypto
			asset.setCrypto(key.replaceAll("USD", ""));
			// Current Price
			asset.setCurrentPrice(currentPrice);
			// Avg Price $
			asset.setAvgPrice(avgPrice);
			// Units
			asset.setUnits(units);
			// Cost $
			asset.setCost(cost);
			// Profile Allocaction %
			asset.setProfileAllocationPercentage((cost / overallTotal) * 100);
			asset.setPnlPercent(pnl);
			// P/L $
			totalPLDollar += pnl * cost / 100;
			asset.setPnl(pnl * cost / 10);
			valueDollar = currentPrice * units;
			totalValueDollar += valueDollar;

			asset.setValueDollar(valueDollar);
			assetList.add(asset);
		}

		summary.setAssetList(assetList);
		portfolio.setSummary(summary);
		portfolio.setDetails(details);
		appResponse.setPortfolio(portfolio);
		return appResponse;
	}
}

Step 8. Write a program that consumes the /getPortfolio endpoint. It can be a front-end application, Java servlet, PHP, Python web application that will consume the JSON response from the REST API endpoint.

Areas of Improvement:

  1. Include the fees for a more precise Profit/Loss calculation.
  2. Improve the time and space complexity of the algorithm.
  3. Call Binance API for a real-time Order History instead of downloading the history into a CSV file.
  4. The logic for this application only works if the transactions were all in USD
  5. Include the tokens earned as distributions (e.g. VTHO, ATOM)

Cheers! You can now view your Binance Portfolio in a matter of seconds!

The source codes for this project can be downloaded here.