From c63c41e75ff0dc543dfa77fa6413d1f678f1b639 Mon Sep 17 00:00:00 2001 From: Markus Aksli Date: Wed, 14 Apr 2021 16:10:10 +0300 Subject: [PATCH 1/8] Remove BuySell (transition to LocalAccount instance methods), rename CurrentAPI to BinanceAPI --- src/main/java/modes/Backtesting.java | 5 +- src/main/java/modes/Collection.java | 8 +- src/main/java/modes/Live.java | 11 +- src/main/java/modes/Simulation.java | 4 +- src/main/java/system/ConfigSetup.java | 8 +- src/main/java/system/Main.java | 10 +- src/main/java/trading/BinanceAPI.java | 28 ++++ src/main/java/trading/BuySell.java | 202 ------------------------ src/main/java/trading/Currency.java | 40 +++-- src/main/java/trading/CurrentAPI.java | 23 --- src/main/java/trading/LocalAccount.java | 188 +++++++++++++++++++++- src/main/java/trading/Trade.java | 8 +- 12 files changed, 263 insertions(+), 272 deletions(-) create mode 100644 src/main/java/trading/BinanceAPI.java delete mode 100644 src/main/java/trading/BuySell.java delete mode 100644 src/main/java/trading/CurrentAPI.java diff --git a/src/main/java/modes/Backtesting.java b/src/main/java/modes/Backtesting.java index 9cecd1c..8e8baf5 100644 --- a/src/main/java/modes/Backtesting.java +++ b/src/main/java/modes/Backtesting.java @@ -30,7 +30,6 @@ public static void startBacktesting() { System.exit(0); } localAccount = new LocalAccount("Investor Toomas", Simulation.STARTING_VALUE); - BuySell.setAccount(localAccount); Scanner sc = new Scanner(System.in); while (true) { System.out.println("\nBacktesting data files:\n"); @@ -47,12 +46,12 @@ public static void startBacktesting() { String path = "backtesting/" + backtestingFiles[index - 1]; try { System.out.println("\n---Setting up..."); - Currency currency = new Currency(new File(path).getName().split("_")[0], path); + Currency currency = new Currency(new File(path).getName().split("_")[0], path, getAccount()); currencies.add(currency); for (Trade trade : localAccount.getActiveTrades()) { trade.setExplanation(trade.getExplanation() + "Manually closed"); - BuySell.close(trade); + getAccount().close(trade); } diff --git a/src/main/java/modes/Collection.java b/src/main/java/modes/Collection.java index bfd559c..e9f6a7a 100644 --- a/src/main/java/modes/Collection.java +++ b/src/main/java/modes/Collection.java @@ -11,7 +11,7 @@ import data.PriceWriter; import org.apache.commons.io.FileUtils; import system.ConfigSetup; -import trading.CurrentAPI; +import trading.BinanceAPI; import system.Formatter; import java.io.*; @@ -40,7 +40,7 @@ public final class Collection { public static final String INTERRUPT_MESSAGE = "Thread interrupted while waiting for request permission"; private static final Semaphore downloadCompletionBlocker = new Semaphore(0); private static final Semaphore requestTracker = new Semaphore(0); - private static final BinanceApiAsyncRestClient client = CurrentAPI.getFactory().newAsyncRestClient(); + private static final BinanceApiAsyncRestClient client = BinanceAPI.getFactory().newAsyncRestClient(); private Collection() { throw new IllegalStateException("Utility class"); @@ -192,7 +192,7 @@ public static void startCollection() { while (true) { try { symbol = sc.nextLine().toUpperCase() + ConfigSetup.getFiat(); - CurrentAPI.get().getPrice(symbol); + BinanceAPI.get().getPrice(symbol); break; } catch (BinanceApiException e) { System.out.println(e.getMessage()); @@ -340,7 +340,7 @@ public static boolean compileBackTestingData(long start, String filename) { } symbol = filename.split("[/\\\\]")[1].split("_")[0]; try (PriceWriter writer = new PriceWriter(filename)) { - List candlesticks = CurrentAPI.get().getCandlestickBars(symbol, CandlestickInterval.FIVE_MINUTES, null, null, start); + List candlesticks = BinanceAPI.get().getCandlestickBars(symbol, CandlestickInterval.FIVE_MINUTES, null, null, start); for (int i = 0; i < candlesticks.size() - 1; i++) { Candlestick candlestick = candlesticks.get(i); writer.writeBean(new PriceBean(candlestick.getCloseTime(), Double.parseDouble(candlestick.getClose()), true)); diff --git a/src/main/java/modes/Live.java b/src/main/java/modes/Live.java index 217fb85..ea798b7 100644 --- a/src/main/java/modes/Live.java +++ b/src/main/java/modes/Live.java @@ -86,7 +86,6 @@ public static void init() { System.out.println(localAccount.getMakerComission() + " Maker commission."); System.out.println(localAccount.getBuyerComission() + " Buyer commission"); System.out.println(localAccount.getTakerComission() + " Taker comission"); - BuySell.setAccount(localAccount); //TODO: Open price for existing currencies String current = ""; @@ -96,14 +95,14 @@ public static void init() { if (balance.getFree().matches("0\\.0+")) continue; if (ConfigSetup.getCurrencies().contains(balance.getAsset())) { current = balance.getAsset(); - Currency balanceCurrency = new Currency(current); + Currency balanceCurrency = new Currency(current, getAccount()); currencies.add(balanceCurrency); addedCurrencies.add(current); double amount = Double.parseDouble(balance.getFree()); localAccount.getWallet().put(balanceCurrency, amount); - double price = Double.parseDouble(CurrentAPI.get().getPrice(current + ConfigSetup.getFiat()).getPrice()); - Optional lotSize = CurrentAPI.get().getExchangeInfo().getSymbolInfo(current + ConfigSetup.getFiat()).getFilters().stream().filter(f -> FilterType.LOT_SIZE == f.getFilterType()).findFirst().map(f1 -> f1.getMinQty()); - Optional minNotational = CurrentAPI.get().getExchangeInfo().getSymbolInfo(current + ConfigSetup.getFiat()).getFilters().stream().filter(f -> FilterType.MIN_NOTIONAL == f.getFilterType()).findFirst().map(SymbolFilter::getMinNotional); + double price = Double.parseDouble(BinanceAPI.get().getPrice(current + ConfigSetup.getFiat()).getPrice()); + Optional lotSize = BinanceAPI.get().getExchangeInfo().getSymbolInfo(current + ConfigSetup.getFiat()).getFilters().stream().filter(f -> FilterType.LOT_SIZE == f.getFilterType()).findFirst().map(f1 -> f1.getMinQty()); + Optional minNotational = BinanceAPI.get().getExchangeInfo().getSymbolInfo(current + ConfigSetup.getFiat()).getFilters().stream().filter(f -> FilterType.MIN_NOTIONAL == f.getFilterType()).findFirst().map(SymbolFilter::getMinNotional); if (lotSize.isPresent()) { if (amount < Double.parseDouble(lotSize.get())) { System.out.println(balance.getFree() + " " + current + " is less than LOT_SIZE " + lotSize.get()); @@ -128,7 +127,7 @@ public static void init() { for (String arg : ConfigSetup.getCurrencies()) { if (!addedCurrencies.contains(arg)) { current = arg; - currencies.add(new Currency(current)); + currencies.add(new Currency(current, getAccount())); } } } catch (Exception e) { diff --git a/src/main/java/modes/Simulation.java b/src/main/java/modes/Simulation.java index a10a669..c3edd94 100644 --- a/src/main/java/modes/Simulation.java +++ b/src/main/java/modes/Simulation.java @@ -3,7 +3,6 @@ import com.binance.api.client.exception.BinanceApiException; import system.ConfigSetup; import trading.LocalAccount; -import trading.BuySell; import trading.Currency; import java.io.IOException; @@ -39,12 +38,11 @@ public static void close() { public static void init() { localAccount = new LocalAccount("Investor Toomas", STARTING_VALUE); - BuySell.setAccount(localAccount); for (String arg : ConfigSetup.getCurrencies()) { //The currency class contains all of the method calls that drive the activity of our bot try { - currencies.add(new Currency(arg)); + currencies.add(new Currency(arg, getAccount())); } catch (BinanceApiException e) { System.out.println("---Could not add " + arg + ConfigSetup.getFiat()); System.out.println(e.getMessage()); diff --git a/src/main/java/system/ConfigSetup.java b/src/main/java/system/ConfigSetup.java index 5fa8cd0..5c1dbe4 100644 --- a/src/main/java/system/ConfigSetup.java +++ b/src/main/java/system/ConfigSetup.java @@ -5,10 +5,8 @@ import indicators.MACD; import indicators.RSI; import modes.Simulation; -import trading.BuySell; +import trading.*; import trading.Currency; -import trading.CurrentAPI; -import trading.Trade; import java.io.BufferedReader; import java.io.File; @@ -17,7 +15,7 @@ import java.util.*; public class ConfigSetup { - private static final int REQUEST_LIMIT = CurrentAPI.get().getExchangeInfo().getRateLimits().stream() + private static final int REQUEST_LIMIT = BinanceAPI.get().getExchangeInfo().getRateLimits().stream() .filter(rateLimit -> rateLimit.getRateLimitType().equals(RateLimitType.REQUEST_WEIGHT)) .findFirst().map(RateLimit::getLimit).orElse(1200); @@ -89,7 +87,7 @@ public static void readConfig() { currencies = Collections.unmodifiableList(Arrays.asList(arr[1].toUpperCase().split(", "))); break; case "Percentage of money per trade": - BuySell.MONEY_PER_TRADE = Double.parseDouble(arr[1]); + LocalAccount.MONEY_PER_TRADE = Double.parseDouble(arr[1]); break; case "Trailing SL": Trade.TRAILING_SL = Double.parseDouble(arr[1]); diff --git a/src/main/java/system/Main.java b/src/main/java/system/Main.java index 25ecbcf..cdfe461 100644 --- a/src/main/java/system/Main.java +++ b/src/main/java/system/Main.java @@ -17,9 +17,11 @@ public static void main(String[] args) { ConfigSetup.readConfig(); } catch (ExceptionInInitializerError cause) { if (cause.getCause() != null) { - if (cause.getCause().getMessage().toLowerCase().contains("banned")) { + if (cause.getCause().getMessage() != null && cause.getCause().getMessage().toLowerCase().contains("banned")) { long bannedTime = Long.parseLong(cause.getCause().getMessage().split("until ")[1].split("\\.")[0]); System.out.println("\nIP Banned by Binance API until " + Formatter.formatDate(bannedTime) + " (" + Formatter.formatDuration(bannedTime - System.currentTimeMillis()) + ")"); + } else { + cause.printStackTrace(); } } new Scanner(System.in).next(); @@ -177,7 +179,7 @@ public void run() { System.out.println("\nID out of range, use \"currencies\" to see valid IDs!"); continue; } - BuySell.open(currencies.get(openIndex - 1), "Trade opened due to: Manually opened\t"); + localAccount.open(currencies.get(openIndex - 1), "Trade opened due to: Manually opened\t"); break; case "close": System.out.println("Enter ID of active trade"); @@ -191,10 +193,10 @@ public void run() { System.out.println("\nID out of range, use \"active\" to see valid IDs!"); continue; } - BuySell.close(localAccount.getActiveTrades().get(closeIndex - 1)); + localAccount.close(localAccount.getActiveTrades().get(closeIndex - 1)); break; case "close all": - localAccount.getActiveTrades().forEach(BuySell::close); + localAccount.getActiveTrades().forEach(localAccount::close); break; case "refresh": if (Mode.get().equals(Mode.LIVE)) { diff --git a/src/main/java/trading/BinanceAPI.java b/src/main/java/trading/BinanceAPI.java new file mode 100644 index 0000000..24c1dc6 --- /dev/null +++ b/src/main/java/trading/BinanceAPI.java @@ -0,0 +1,28 @@ +package trading; + +import com.binance.api.client.BinanceApiClientFactory; +import com.binance.api.client.BinanceApiRestClient; + +public final class BinanceAPI { + private static final BinanceApiClientFactory factory = BinanceApiClientFactory.newInstance(); + private static BinanceApiRestClient defaultClient; + + private BinanceAPI() { + throw new IllegalStateException("Utility class"); + } + + public static BinanceApiClientFactory login(String apiKey, String secretKey) { + return BinanceApiClientFactory.newInstance(apiKey, secretKey); + } + + public static BinanceApiClientFactory getFactory() { + return factory; + } + + public static BinanceApiRestClient get() { + if (defaultClient == null) { + defaultClient = factory.newRestClient(); + } + return defaultClient; + } +} diff --git a/src/main/java/trading/BuySell.java b/src/main/java/trading/BuySell.java deleted file mode 100644 index 5a281be..0000000 --- a/src/main/java/trading/BuySell.java +++ /dev/null @@ -1,202 +0,0 @@ -package trading; - -import com.binance.api.client.BinanceApiRestClient; -import com.binance.api.client.domain.OrderStatus; -import com.binance.api.client.domain.account.NewOrderResponse; -import com.binance.api.client.domain.account.NewOrderResponseType; -import com.binance.api.client.domain.general.FilterType; -import com.binance.api.client.domain.general.SymbolFilter; -import com.binance.api.client.exception.BinanceApiException; -import system.ConfigSetup; -import system.Formatter; -import system.Mode; - -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.Optional; - -import static com.binance.api.client.domain.account.NewOrder.marketBuy; -import static com.binance.api.client.domain.account.NewOrder.marketSell; - -public class BuySell { - - private static LocalAccount localAccount; - public static double MONEY_PER_TRADE; - - public static void setAccount(LocalAccount localAccount) { - BuySell.localAccount = localAccount; - } - - public static LocalAccount getAccount() { - return localAccount; - } - - private BuySell() { - throw new IllegalStateException("Utility class"); - } - - public static boolean enoughFunds() { - return nextAmount() != 0; - } - - //Used by strategy - public static void open(Currency currency, String explanation) { - if (currency.hasActiveTrade()) { - System.out.println("---Cannot open trade since there already is an open trade for " + currency.getPair() + "!"); - return; - } - if (!enoughFunds()) { - System.out.println("---Out of funds, cannot open trade! (" + Formatter.formatDecimal(localAccount.getFiat()) + ")"); - return; //If no fiat is available, we cant trade - } - - double currentPrice = currency.getPrice(); //Current price of the currency - double fiatCost = nextAmount(); - double amount = fiatCost / currency.getPrice(); - - Trade trade; - if (Mode.get().equals(Mode.LIVE)) { - NewOrderResponse order = placeOrder(currency, amount, true); - if (order == null) { - return; - } - double fillsQty = 0; - double fillsPrice = 0; - - for (com.binance.api.client.domain.account.Trade fill : order.getFills()) { - double qty = Double.parseDouble(fill.getQty()); - fillsQty += qty - Double.parseDouble(fill.getCommission()); - fillsPrice += qty * Double.parseDouble(fill.getPrice()); - } - System.out.println("Got filled for " + BigDecimal.valueOf(fillsQty).toString() - + " at " + Formatter.formatDate(order.getTransactTime()) - + ", at a price of " + Formatter.formatDecimal(fillsPrice) + " " + ConfigSetup.getFiat()); - fiatCost = fillsPrice; - amount = fillsQty; - trade = new Trade(currency, fillsPrice / fillsQty, amount, explanation); - System.out.println("Opened trade at an avg open of " + Formatter.formatDecimal(trade.getEntryPrice()) + " (" - + Formatter.formatPercent((trade.getEntryPrice() - currentPrice) / trade.getEntryPrice()) - + " from current)"); - } else { - trade = new Trade(currency, currentPrice, amount, explanation); - } - - currency.setActiveTrade(trade); - - //Converting fiat value to coin value - localAccount.addToFiat(-fiatCost); - localAccount.addToWallet(currency, amount); - localAccount.openTrade(trade); - - String message = "---" + Formatter.formatDate(trade.getOpenTime()) - + " opened trade (" + Formatter.formatDecimal(trade.getAmount()) + " " - + currency.getPair() + "), at " + Formatter.formatDecimal(trade.getEntryPrice()) - + ", " + trade.getExplanation(); - System.out.println(message); - if (Mode.get().equals(Mode.BACKTESTING)) currency.appendLogLine(message); - } - - //Used by trade - public static void close(Trade trade) { - if (Mode.get().equals(Mode.LIVE)) { - NewOrderResponse order = placeOrder(trade.getCurrency(), trade.getAmount(), false); - if (order == null) { - return; - } - double fillsQty = 0; - double fillsPrice = 0; - for (com.binance.api.client.domain.account.Trade fill : order.getFills()) { - double qty = Double.parseDouble(fill.getQty()); - fillsQty += qty; - fillsPrice += qty * Double.parseDouble(fill.getPrice()) - Double.parseDouble(fill.getCommission()); - } - System.out.println("Got filled for " + BigDecimal.valueOf(fillsQty).toString() - + " at " + Formatter.formatDate(order.getTransactTime()) - + ", at a price of " + Formatter.formatDecimal(fillsPrice) + " " + ConfigSetup.getFiat()); - trade.setClosePrice(fillsPrice / fillsQty); - trade.setCloseTime(order.getTransactTime()); - localAccount.removeFromWallet(trade.getCurrency(), fillsQty); - localAccount.addToFiat(fillsPrice); - System.out.println("Closed trade at an avg close of " + Formatter.formatDecimal(trade.getClosePrice()) + " (" - + Formatter.formatPercent((trade.getClosePrice() - trade.getCurrency().getPrice()) / trade.getClosePrice()) - + " from current)"); - } else { - trade.setClosePrice(trade.getCurrency().getPrice()); - trade.setCloseTime(trade.getCurrency().getCurrentTime()); - localAccount.removeFromWallet(trade.getCurrency(), trade.getAmount()); - localAccount.addToFiat(trade.getAmount() * trade.getClosePrice()); - } - - //Converting coin value back to fiat - localAccount.closeTrade(trade); - trade.getCurrency().setActiveTrade(null); - - String message = "---" + (Formatter.formatDate(trade.getCloseTime())) + " closed trade (" - + Formatter.formatDecimal(trade.getAmount()) + " " + trade.getCurrency().getPair() - + "), at " + Formatter.formatDecimal(trade.getClosePrice()) - + ", with " + Formatter.formatPercent(trade.getProfit()) + " profit" - + "\n------" + trade.getExplanation(); - System.out.println(message); - if (Mode.get().equals(Mode.BACKTESTING)) trade.getCurrency().appendLogLine(message); - } - - private static double nextAmount() { - if (Mode.get().equals(Mode.BACKTESTING)) return localAccount.getFiat(); - return Math.min(localAccount.getFiat(), localAccount.getTotalValue() * MONEY_PER_TRADE); - } - - - //TODO: Implement limit ordering - public static NewOrderResponse placeOrder(Currency currency, double amount, boolean buy) { - System.out.println("\n---Placing a " + (buy ? "buy" : "sell") + " market order for " + currency.getPair()); - BigDecimal originalDecimal = BigDecimal.valueOf(amount); - //Round amount to base precision and LOT_SIZE - int precision = CurrentAPI.get().getExchangeInfo().getSymbolInfo(currency.getPair()).getBaseAssetPrecision(); - String lotSize; - Optional minQtyOptional = CurrentAPI.get().getExchangeInfo().getSymbolInfo(currency.getPair()).getFilters().stream().filter(f -> FilterType.LOT_SIZE == f.getFilterType()).findFirst().map(f1 -> f1.getMinQty()); - Optional minNotational = CurrentAPI.get().getExchangeInfo().getSymbolInfo(currency.getPair()).getFilters().stream().filter(f -> FilterType.MIN_NOTIONAL == f.getFilterType()).findFirst().map(SymbolFilter::getMinNotional); - if (minQtyOptional.isPresent()) { - lotSize = minQtyOptional.get(); - } else { - System.out.println("---Could not get LOT_SIZE so could not open trade!"); - return null; - } - double minQtyDouble = Double.parseDouble(lotSize); - - //Check LOT_SIZE to make sure amount is not too small - if (amount < minQtyDouble) { - System.out.println("---Amount smaller than min LOT_SIZE, could not open trade! (min LOT_SIZE=" + lotSize + ", amount=" + amount); - return null; - } - - //Convert amount to an integer multiple of LOT_SIZE and convert to asset precision - System.out.println("Converting from double trade amount " + originalDecimal.toString() + " to base asset precision " + precision + " LOT_SIZE " + lotSize); - String convertedAmount = new BigDecimal(lotSize).multiply(new BigDecimal((int) (amount / minQtyDouble))).setScale(precision, RoundingMode.HALF_DOWN).toString(); - System.out.println("Converted to " + convertedAmount); - - if (minNotational.isPresent()) { - double notational = Double.parseDouble(convertedAmount) * currency.getPrice(); - if (notational < Double.parseDouble(minNotational.get())) { - System.out.println("---Cannot open trade because notational value " + Formatter.formatDecimal(notational) + " is smaller than minimum " + minNotational.get()); - } - } - - NewOrderResponse order; - try { - BinanceApiRestClient client = CurrentAPI.get(); - order = client.newOrder( - buy ? - marketBuy(currency.getPair(), convertedAmount).newOrderRespType(NewOrderResponseType.FULL) : - marketSell(currency.getPair(), convertedAmount).newOrderRespType(NewOrderResponseType.FULL)); - System.out.println("---Executed a " + order.getSide() + " order with id " + order.getClientOrderId() + " for " + convertedAmount + " " + currency.getPair()); - if (!order.getStatus().equals(OrderStatus.FILLED)) { - System.out.println("Order is " + order.getStatus() + ", not FILLED!"); - } - return order; - } catch (BinanceApiException e) { - System.out.println("---Failed " + (buy ? "buy" : "sell") + " " + convertedAmount + " " + currency.getPair()); - System.out.println(e.getMessage()); - return null; - } - } -} diff --git a/src/main/java/trading/Currency.java b/src/main/java/trading/Currency.java index 721b64c..80cb571 100644 --- a/src/main/java/trading/Currency.java +++ b/src/main/java/trading/Currency.java @@ -1,10 +1,10 @@ package trading; -import data.PriceBean; -import data.PriceReader; import com.binance.api.client.BinanceApiWebSocketClient; import com.binance.api.client.domain.market.Candlestick; import com.binance.api.client.domain.market.CandlestickInterval; +import data.PriceBean; +import data.PriceReader; import indicators.DBB; import indicators.Indicator; import indicators.MACD; @@ -20,7 +20,9 @@ import java.time.Duration; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; -import java.util.*; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; @@ -28,6 +30,7 @@ public class Currency implements Closeable { public static int CONFLUENCE_TARGET; private final String pair; + private LocalAccount account; private Trade activeTrade; private long candleTime; private final List indicators = new ArrayList<>(); @@ -43,11 +46,12 @@ public class Currency implements Closeable { private Closeable apiListener; //Used for SIMULATION and LIVE - public Currency(String coin) { + public Currency(String coin, LocalAccount account) { this.pair = coin + ConfigSetup.getFiat(); + this.account = account; //Every currency needs to contain and update our indicators - List history = CurrentAPI.get().getCandlestickBars(pair, CandlestickInterval.FIVE_MINUTES); + List history = BinanceAPI.get().getCandlestickBars(pair, CandlestickInterval.FIVE_MINUTES); List closingPrices = history.stream().map(candle -> Double.parseDouble(candle.getClose())).collect(Collectors.toList()); indicators.add(new RSI(closingPrices, 14)); indicators.add(new MACD(closingPrices, 12, 26, 9)); @@ -58,11 +62,9 @@ public Currency(String coin) { candleTime = history.get(history.size() - 1).getCloseTime(); currentPrice = Double.parseDouble(history.get(history.size() - 1).getClose()); - BinanceApiWebSocketClient client = CurrentAPI.getFactory().newWebSocketClient(); + BinanceApiWebSocketClient client = BinanceAPI.getFactory().newWebSocketClient(); //We add a websocket listener that automatically updates our values and triggers our strategy or trade logic as needed apiListener = client.onAggTradeEvent(pair.toLowerCase(), response -> { - //Every message and the resulting indicator and strategy calculations is handled concurrently - //System.out.println(Thread.currentThread().getId()); double newPrice = Double.parseDouble(response.getPrice()); long newTime = response.getEventTime(); @@ -82,8 +84,10 @@ public Currency(String coin) { } //Used for BACKTESTING - public Currency(String pair, String filePath) { + public Currency(String pair, String filePath, LocalAccount account) { this.pair = pair; + this.account = account; + try (PriceReader reader = new PriceReader(filePath)) { PriceBean bean = reader.readPrice(); @@ -119,7 +123,7 @@ private void accept(PriceBean bean) { if (bean.isClosing()) { indicators.forEach(indicator -> indicator.update(bean.getPrice())); if (Mode.get().equals(Mode.BACKTESTING)) { - appendLogLine(system.Formatter.formatDate(currentTime) + " "); + appendLogLine(system.Formatter.formatDate(currentTime) + " " + this); } } @@ -127,13 +131,13 @@ private void accept(PriceBean bean) { int confluence = 0; //0 Confluence should be reserved in the config for doing nothing currentlyCalculating.set(true); //We can disable the strategy and trading logic to only check indicator and price accuracy - if ((Trade.CLOSE_USE_CONFLUENCE && hasActiveTrade()) || BuySell.enoughFunds()) { + if ((Trade.CLOSE_USE_CONFLUENCE && hasActiveTrade()) || account.enoughFunds()) { confluence = check(); } if (hasActiveTrade()) { //We only allow one active trade per currency, this means we only need to do one of the following: activeTrade.update(currentPrice, confluence);//Update the active trade stop-loss and high values - } else if (confluence >= CONFLUENCE_TARGET && BuySell.enoughFunds()) { - BuySell.open(Currency.this, "Trade opened due to: " + getExplanations()); + } else if (confluence >= CONFLUENCE_TARGET && account.enoughFunds()) { + account.open(Currency.this, "Trade opened due to: " + getExplanations()); } currentlyCalculating.set(false); } @@ -181,8 +185,12 @@ public void appendLogLine(String s) { log.append(s).append("\n"); } + public LocalAccount getAccount() { + return account; + } + public void log(String path) { - List tradeHistory = new ArrayList<>(BuySell.getAccount().getTradeHistory()); + List tradeHistory = new ArrayList<>(account.getTradeHistory()); try (FileWriter writer = new FileWriter(path)) { writer.write("Test ended " + system.Formatter.formatDate(LocalDateTime.now()) + " \n"); writer.write("\n\nCONFIG:\n"); @@ -211,8 +219,8 @@ public void log(String path) { double tradePerWeek = 604800000.0 / (((double) currentTime - firstBean.getTimestamp()) / tradeHistory.size()); - writer.write("\nBot performance: " + system.Formatter.formatPercent(BuySell.getAccount().getProfit()) + "\n\n"); - writer.write(BuySell.getAccount().getTradeHistory().size() + " closed trades" + writer.write("\nBot performance: " + system.Formatter.formatPercent(account.getProfit()) + "\n\n"); + writer.write(account.getTradeHistory().size() + " closed trades" + " (" + system.Formatter.formatDecimal(tradePerWeek) + " trades per week) with an average holding length of " + system.Formatter.formatDuration(Duration.of(tradeDurs / tradeHistory.size(), ChronoUnit.MILLIS)) + " hours"); if (lossTrades != 0) { diff --git a/src/main/java/trading/CurrentAPI.java b/src/main/java/trading/CurrentAPI.java deleted file mode 100644 index c58ce82..0000000 --- a/src/main/java/trading/CurrentAPI.java +++ /dev/null @@ -1,23 +0,0 @@ -package trading; - -import com.binance.api.client.BinanceApiClientFactory; -import com.binance.api.client.BinanceApiRestClient; - -public final class CurrentAPI { - private static BinanceApiClientFactory factory; - - public static void login(String apiKey, String secretKey) { - factory = BinanceApiClientFactory.newInstance(apiKey, secretKey); - } - - public static BinanceApiClientFactory getFactory() { - if (factory == null) { - factory = BinanceApiClientFactory.newInstance(); - } - return factory; - } - - public static BinanceApiRestClient get() { - return getFactory().newRestClient(); - } -} diff --git a/src/main/java/trading/LocalAccount.java b/src/main/java/trading/LocalAccount.java index 99d7b21..ef67a61 100644 --- a/src/main/java/trading/LocalAccount.java +++ b/src/main/java/trading/LocalAccount.java @@ -1,19 +1,37 @@ package trading; +import com.binance.api.client.BinanceApiClientFactory; +import com.binance.api.client.BinanceApiRestClient; +import com.binance.api.client.domain.OrderStatus; import com.binance.api.client.domain.account.Account; +import com.binance.api.client.domain.account.NewOrderResponse; +import com.binance.api.client.domain.account.NewOrderResponseType; +import com.binance.api.client.domain.general.FilterType; +import com.binance.api.client.domain.general.SymbolFilter; import com.binance.api.client.exception.BinanceApiException; import system.ConfigSetup; import system.Formatter; +import system.Mode; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; +import static com.binance.api.client.domain.account.NewOrder.marketBuy; +import static com.binance.api.client.domain.account.NewOrder.marketSell; + public class LocalAccount { + public static double MONEY_PER_TRADE; + private final String username; private Account realAccount; + private BinanceApiClientFactory apiFactory; + private BinanceApiRestClient client; //To give the account a specific final amount of money. private double fiatValue; @@ -39,12 +57,14 @@ public LocalAccount(String username, double startingValue) { } public LocalAccount(String apiKey, String secretApiKey) { - CurrentAPI.login(apiKey, secretApiKey); + apiFactory = BinanceAPI.login(apiKey, secretApiKey); + client = apiFactory.newRestClient(); + username = ""; wallet = new ConcurrentHashMap<>(); tradeHistory = new ArrayList<>(); activeTrades = new CopyOnWriteArrayList<>(); - realAccount = CurrentAPI.get().getAccount(); + realAccount = client.getAccount(); if (!realAccount.isCanTrade()) { System.out.println("Can't trade!"); } @@ -172,4 +192,168 @@ public double getTakerComission() { public double getBuyerComission() { return buyerComission; } + + public boolean enoughFunds() { + return nextAmount() != 0; + } + + //Used by strategy + public void open(Currency currency, String explanation) { + if (currency.hasActiveTrade()) { + System.out.println("---Cannot open trade since there already is an open trade for " + currency.getPair() + "!"); + return; + } + if (!enoughFunds()) { + System.out.println("---Out of funds, cannot open trade! (" + Formatter.formatDecimal(getFiat()) + ")"); + return; //If no fiat is available, we cant trade + } + + double currentPrice = currency.getPrice(); //Current price of the currency + double fiatCost = nextAmount(); + double amount = fiatCost / currency.getPrice(); + + Trade trade; + if (Mode.get().equals(Mode.LIVE)) { + NewOrderResponse order = placeOrder(currency, amount, true); + if (order == null) { + return; + } + double fillsQty = 0; + double fillsPrice = 0; + + for (com.binance.api.client.domain.account.Trade fill : order.getFills()) { + double qty = Double.parseDouble(fill.getQty()); + fillsQty += qty - Double.parseDouble(fill.getCommission()); + fillsPrice += qty * Double.parseDouble(fill.getPrice()); + } + System.out.println("Got filled for " + BigDecimal.valueOf(fillsQty).toString() + + " at " + Formatter.formatDate(order.getTransactTime()) + + ", at a price of " + Formatter.formatDecimal(fillsPrice) + " " + ConfigSetup.getFiat()); + fiatCost = fillsPrice; + amount = fillsQty; + trade = new Trade(currency, fillsPrice / fillsQty, amount, explanation); + System.out.println("Opened trade at an avg open of " + Formatter.formatDecimal(trade.getEntryPrice()) + " (" + + Formatter.formatPercent((trade.getEntryPrice() - currentPrice) / trade.getEntryPrice()) + + " from current)"); + } else { + trade = new Trade(currency, currentPrice, amount, explanation); + } + + currency.setActiveTrade(trade); + + //Converting fiat value to coin value + addToFiat(-fiatCost); + addToWallet(currency, amount); + openTrade(trade); + + String message = "---" + Formatter.formatDate(trade.getOpenTime()) + + " opened trade (" + Formatter.formatDecimal(trade.getAmount()) + " " + + currency.getPair() + "), at " + Formatter.formatDecimal(trade.getEntryPrice()) + + ", " + trade.getExplanation(); + System.out.println(message); + if (Mode.get().equals(Mode.BACKTESTING)) currency.appendLogLine(message); + } + + //Used by trade + public void close(Trade trade) { + if (Mode.get().equals(Mode.LIVE)) { + NewOrderResponse order = placeOrder(trade.getCurrency(), trade.getAmount(), false); + if (order == null) { + return; + } + double fillsQty = 0; + double fillsPrice = 0; + for (com.binance.api.client.domain.account.Trade fill : order.getFills()) { + double qty = Double.parseDouble(fill.getQty()); + fillsQty += qty; + fillsPrice += qty * Double.parseDouble(fill.getPrice()) - Double.parseDouble(fill.getCommission()); + } + System.out.println("Got filled for " + BigDecimal.valueOf(fillsQty).toString() + + " at " + Formatter.formatDate(order.getTransactTime()) + + ", at a price of " + Formatter.formatDecimal(fillsPrice) + " " + ConfigSetup.getFiat()); + trade.setClosePrice(fillsPrice / fillsQty); + trade.setCloseTime(order.getTransactTime()); + removeFromWallet(trade.getCurrency(), fillsQty); + addToFiat(fillsPrice); + System.out.println("Closed trade at an avg close of " + Formatter.formatDecimal(trade.getClosePrice()) + " (" + + Formatter.formatPercent((trade.getClosePrice() - trade.getCurrency().getPrice()) / trade.getClosePrice()) + + " from current)"); + } else { + trade.setClosePrice(trade.getCurrency().getPrice()); + trade.setCloseTime(trade.getCurrency().getCurrentTime()); + removeFromWallet(trade.getCurrency(), trade.getAmount()); + addToFiat(trade.getAmount() * trade.getClosePrice()); + } + + //Converting coin value back to fiat + closeTrade(trade); + trade.getCurrency().setActiveTrade(null); + + String message = "---" + (Formatter.formatDate(trade.getCloseTime())) + " closed trade (" + + Formatter.formatDecimal(trade.getAmount()) + " " + trade.getCurrency().getPair() + + "), at " + Formatter.formatDecimal(trade.getClosePrice()) + + ", with " + Formatter.formatPercent(trade.getProfit()) + " profit" + + "\n------" + trade.getExplanation(); + System.out.println(message); + if (Mode.get().equals(Mode.BACKTESTING)) trade.getCurrency().appendLogLine(message); + } + + private double nextAmount() { + if (Mode.get().equals(Mode.BACKTESTING)) return getFiat(); + return Math.min(getFiat(), getTotalValue() * MONEY_PER_TRADE); + } + + + //TODO: Implement limit ordering + private NewOrderResponse placeOrder(Currency currency, double amount, boolean buy) { + System.out.println("\n---Placing a " + (buy ? "buy" : "sell") + " market order for " + currency.getPair()); + BigDecimal originalDecimal = BigDecimal.valueOf(amount); + //Round amount to base precision and LOT_SIZE + int precision = client.getExchangeInfo().getSymbolInfo(currency.getPair()).getBaseAssetPrecision(); + String lotSize; + Optional minQtyOptional = client.getExchangeInfo().getSymbolInfo(currency.getPair()).getFilters().stream().filter(f -> FilterType.LOT_SIZE == f.getFilterType()).findFirst().map(f1 -> f1.getMinQty()); + Optional minNotational = client.getExchangeInfo().getSymbolInfo(currency.getPair()).getFilters().stream().filter(f -> FilterType.MIN_NOTIONAL == f.getFilterType()).findFirst().map(SymbolFilter::getMinNotional); + if (minQtyOptional.isPresent()) { + lotSize = minQtyOptional.get(); + } else { + System.out.println("---Could not get LOT_SIZE so could not open trade!"); + return null; + } + double minQtyDouble = Double.parseDouble(lotSize); + + //Check LOT_SIZE to make sure amount is not too small + if (amount < minQtyDouble) { + System.out.println("---Amount smaller than min LOT_SIZE, could not open trade! (min LOT_SIZE=" + lotSize + ", amount=" + amount); + return null; + } + + //Convert amount to an integer multiple of LOT_SIZE and convert to asset precision + System.out.println("Converting from double trade amount " + originalDecimal.toString() + " to base asset precision " + precision + " LOT_SIZE " + lotSize); + String convertedAmount = new BigDecimal(lotSize).multiply(new BigDecimal((int) (amount / minQtyDouble))).setScale(precision, RoundingMode.HALF_DOWN).toString(); + System.out.println("Converted to " + convertedAmount); + + if (minNotational.isPresent()) { + double notational = Double.parseDouble(convertedAmount) * currency.getPrice(); + if (notational < Double.parseDouble(minNotational.get())) { + System.out.println("---Cannot open trade because notational value " + Formatter.formatDecimal(notational) + " is smaller than minimum " + minNotational.get()); + } + } + + NewOrderResponse order; + try { + order = client.newOrder( + buy ? + marketBuy(currency.getPair(), convertedAmount).newOrderRespType(NewOrderResponseType.FULL) : + marketSell(currency.getPair(), convertedAmount).newOrderRespType(NewOrderResponseType.FULL)); + System.out.println("---Executed a " + order.getSide() + " order with id " + order.getClientOrderId() + " for " + convertedAmount + " " + currency.getPair()); + if (!order.getStatus().equals(OrderStatus.FILLED)) { + System.out.println("Order is " + order.getStatus() + ", not FILLED!"); + } + return order; + } catch (BinanceApiException e) { + System.out.println("---Failed " + (buy ? "buy" : "sell") + " " + convertedAmount + " " + currency.getPair()); + System.out.println(e.getMessage()); + return null; + } + } } diff --git a/src/main/java/trading/Trade.java b/src/main/java/trading/Trade.java index 57b48cb..1c1c543 100644 --- a/src/main/java/trading/Trade.java +++ b/src/main/java/trading/Trade.java @@ -99,25 +99,25 @@ public void update(double newPrice, int confluence) { if (getProfit() > TAKE_PROFIT) { explanation += "Closed due to: Take profit"; - BuySell.close(this); + currency.getAccount().close(this); return; } if (newPrice < high * (1 - TRAILING_SL)) { explanation += "Closed due to: Trailing SL"; - BuySell.close(this); + currency.getAccount().close(this); return; } if (CLOSE_USE_CONFLUENCE && confluence <= -CLOSE_CONFLUENCE) { explanation += "Closed due to: Indicator confluence of " + confluence; - BuySell.close(this); + currency.getAccount().close(this); } } @Override public String toString() { - return (isClosed() ? (BuySell.getAccount().getTradeHistory().indexOf(this) + 1) : (BuySell.getAccount().getActiveTrades().indexOf(this) + 1)) + " " + return (isClosed() ? (currency.getAccount().getTradeHistory().indexOf(this) + 1) : (currency.getAccount().getActiveTrades().indexOf(this) + 1)) + " " + currency.getPair() + " " + Formatter.formatDecimal(amount) + "\n" + "open: " + Formatter.formatDate(openTime) + " at " + entryPrice + "\n" + (isClosed() ? "close: " + Formatter.formatDate(closeTime) + " at " + closePrice : "current price: " + currency.getPrice()) + "\n" From 0a574694ff1a84b4a839273b02d7b72172c21ec8 Mon Sep 17 00:00:00 2001 From: Markus Aksli Date: Wed, 14 Apr 2021 17:55:18 +0300 Subject: [PATCH 2/8] Remove modes and create Instance class instead --- src/main/java/modes/Backtesting.java | 75 ---- src/main/java/modes/Live.java | 174 --------- src/main/java/modes/Simulation.java | 52 --- .../java/{trading => system}/BinanceAPI.java | 2 +- .../java/{modes => system}/Collection.java | 5 +- src/main/java/system/ConfigSetup.java | 3 +- src/main/java/system/Main.java | 170 -------- src/main/java/system/Mode.java | 31 -- src/main/java/trading/Currency.java | 7 +- src/main/java/trading/Instance.java | 368 ++++++++++++++++++ src/main/java/trading/LocalAccount.java | 31 +- 11 files changed, 389 insertions(+), 529 deletions(-) delete mode 100644 src/main/java/modes/Backtesting.java delete mode 100644 src/main/java/modes/Live.java delete mode 100644 src/main/java/modes/Simulation.java rename src/main/java/{trading => system}/BinanceAPI.java (97%) rename src/main/java/{modes => system}/Collection.java (99%) delete mode 100644 src/main/java/system/Mode.java create mode 100644 src/main/java/trading/Instance.java diff --git a/src/main/java/modes/Backtesting.java b/src/main/java/modes/Backtesting.java deleted file mode 100644 index 8e8baf5..0000000 --- a/src/main/java/modes/Backtesting.java +++ /dev/null @@ -1,75 +0,0 @@ -package modes; - -import trading.*; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Scanner; - -public final class Backtesting { - private static final List currencies = new ArrayList<>(); - private static LocalAccount localAccount; - - private Backtesting() { - throw new IllegalStateException("Utility class"); - } - - public static List getCurrencies() { - return currencies; - } - - public static LocalAccount getAccount() { - return localAccount; - } - - public static void startBacktesting() { - final String[] backtestingFiles = Collection.getDataFiles(); - if (backtestingFiles.length == 0) { - System.out.println("No backtesting files detected!"); - System.exit(0); - } - localAccount = new LocalAccount("Investor Toomas", Simulation.STARTING_VALUE); - Scanner sc = new Scanner(System.in); - while (true) { - System.out.println("\nBacktesting data files:\n"); - for (int i = 0; i < backtestingFiles.length; i++) { - System.out.println("[" + (i + 1) + "] " + backtestingFiles[i]); - } - System.out.println("\nEnter a number to select the backtesting data file"); - String input = sc.nextLine(); - if (!input.matches("\\d+")) continue; - int index = Integer.parseInt(input); - if (index > backtestingFiles.length) { - continue; - } - String path = "backtesting/" + backtestingFiles[index - 1]; - try { - System.out.println("\n---Setting up..."); - Currency currency = new Currency(new File(path).getName().split("_")[0], path, getAccount()); - currencies.add(currency); - - for (Trade trade : localAccount.getActiveTrades()) { - trade.setExplanation(trade.getExplanation() + "Manually closed"); - getAccount().close(trade); - } - - - int i = 1; - path = path.replace("backtesting", "log"); - String resultPath = path.replace(".dat", "_run_" + i + ".txt"); - while (new File(resultPath).exists()) { - i++; - resultPath = path.replace(".dat", "_run_" + i + ".txt"); - } - new File("log").mkdir(); - - currency.log(resultPath); - break; - } catch (Exception e) { - e.printStackTrace(); - System.out.println("Testing failed, try again"); - } - } - } -} diff --git a/src/main/java/modes/Live.java b/src/main/java/modes/Live.java deleted file mode 100644 index ea798b7..0000000 --- a/src/main/java/modes/Live.java +++ /dev/null @@ -1,174 +0,0 @@ -package modes; - -import com.binance.api.client.domain.account.AssetBalance; -import com.binance.api.client.domain.general.FilterType; -import com.binance.api.client.domain.general.SymbolFilter; -import system.ConfigSetup; -import system.Formatter; -import trading.*; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.Scanner; - - -public final class Live { - private static LocalAccount localAccount; - private static final List currencies = new ArrayList<>(); - private static final File credentialsFile = new File("credentials.txt"); - - private Live() { - throw new IllegalStateException("Utility class"); - } - - public static LocalAccount getAccount() { - return localAccount; - } - - public static List getCurrencies() { - return currencies; - } - - public static void close() { - for (Currency currency : currencies) { - try { - currency.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - public static void init() { - boolean fileFailed = true; - if (credentialsFile.exists()) { - //TODO: This try block doesn't work - try { - final List strings = Files.readAllLines(credentialsFile.toPath()); - if (!strings.get(0).matches("\\*+")) { - localAccount = new LocalAccount(strings.get(0), strings.get(1)); - fileFailed = false; - } else { - System.out.println("---credentials.txt has not been set up"); - } - } catch (Exception e) { - e.printStackTrace(); - System.out.println("---Failed to use credentials in credentials.txt"); - } - } else { - System.out.println("---credentials.txt file not detected!"); - } - if (fileFailed) { - Scanner sc = new Scanner(System.in); - String apiKey; - String apiSecret; - while (true) { - System.out.println("Enter your API Key: "); - apiKey = sc.nextLine(); - if (apiKey.length() == 64) { - System.out.println("Enter your Secret Key: "); - apiSecret = sc.nextLine(); - if (apiSecret.length() == 64) { - break; - } else System.out.println("Secret API is incorrect, enter again."); - } else System.out.println("Incorrect API, enter again."); - } - localAccount = new LocalAccount(apiKey, apiSecret); - } - - //This doesn't seem to do anything - //localAccount.getRealAccount().setUpdateTime(1); - System.out.println("Can trade: " + localAccount.getRealAccount().isCanTrade()); - System.out.println(localAccount.getMakerComission() + " Maker commission."); - System.out.println(localAccount.getBuyerComission() + " Buyer commission"); - System.out.println(localAccount.getTakerComission() + " Taker comission"); - - //TODO: Open price for existing currencies - String current = ""; - try { - List addedCurrencies = new ArrayList<>(); - for (AssetBalance balance : localAccount.getRealAccount().getBalances()) { - if (balance.getFree().matches("0\\.0+")) continue; - if (ConfigSetup.getCurrencies().contains(balance.getAsset())) { - current = balance.getAsset(); - Currency balanceCurrency = new Currency(current, getAccount()); - currencies.add(balanceCurrency); - addedCurrencies.add(current); - double amount = Double.parseDouble(balance.getFree()); - localAccount.getWallet().put(balanceCurrency, amount); - double price = Double.parseDouble(BinanceAPI.get().getPrice(current + ConfigSetup.getFiat()).getPrice()); - Optional lotSize = BinanceAPI.get().getExchangeInfo().getSymbolInfo(current + ConfigSetup.getFiat()).getFilters().stream().filter(f -> FilterType.LOT_SIZE == f.getFilterType()).findFirst().map(f1 -> f1.getMinQty()); - Optional minNotational = BinanceAPI.get().getExchangeInfo().getSymbolInfo(current + ConfigSetup.getFiat()).getFilters().stream().filter(f -> FilterType.MIN_NOTIONAL == f.getFilterType()).findFirst().map(SymbolFilter::getMinNotional); - if (lotSize.isPresent()) { - if (amount < Double.parseDouble(lotSize.get())) { - System.out.println(balance.getFree() + " " + current + " is less than LOT_SIZE " + lotSize.get()); - continue; - } - } - if (minNotational.isPresent()) { - if (amount * price < Double.parseDouble(minNotational.get())) { - System.out.println(current + " notational value of " - + Formatter.formatDecimal(amount * price) + " is less than min notational " - + minNotational.get()); - continue; - } - } - final Trade trade = new Trade(balanceCurrency, balanceCurrency.getPrice(), amount, "Trade opened due to: Added based on live account\t"); - localAccount.getActiveTrades().add(trade); - balanceCurrency.setActiveTrade(trade); - System.out.println("Added an active trade of " + balance.getFree() + " " + current + " at " + Formatter.formatDecimal(trade.getEntryPrice()) + " based on existing balance in account"); - } - } - localAccount.setStartingValue(localAccount.getTotalValue()); - for (String arg : ConfigSetup.getCurrencies()) { - if (!addedCurrencies.contains(arg)) { - current = arg; - currencies.add(new Currency(current, getAccount())); - } - } - } catch (Exception e) { - System.out.println("---Could not add " + current + ConfigSetup.getFiat()); - System.out.println(e.getMessage()); - } - } - - public static void refreshWalletAndTrades() { - for (AssetBalance balance : localAccount.getRealAccount().getBalances()) { - if (balance.getFree().matches("0\\.0+")) continue; - if (balance.getAsset().equals(ConfigSetup.getFiat())) { - final double amount = Double.parseDouble(balance.getFree()); - if (localAccount.getFiat() != amount) { - System.out.println("---Refreshed " + balance.getAsset() + " from " + Formatter.formatDecimal(localAccount.getFiat()) + " to " + amount); - System.out.println(balance.getLocked()); - localAccount.setFiat(amount); - } - continue; - } - for (Currency currency : currencies) { - if ((balance.getAsset() + ConfigSetup.getFiat()).equals(currency.getPair())) { - final double amount = Double.parseDouble(balance.getFree()); - if (!localAccount.getWallet().containsKey(currency)) { - System.out.println("---Refreshed " + currency.getPair() + " from 0 to " + balance.getFree()); - localAccount.getWallet().replace(currency, amount); - } - if (localAccount.getWallet().get(currency) != amount) { - System.out.println("---Refreshed " + currency.getPair() + " from " + Formatter.formatDecimal(localAccount.getWallet().get(currency)) + " to " + balance.getFree()); - System.out.println(balance.getLocked()); - localAccount.getWallet().replace(currency, amount); - } - if (currency.hasActiveTrade()) { - if (currency.getActiveTrade().getAmount() > amount) { - System.out.println("---Refreshed " + currency.getPair() + " trade from " + Formatter.formatDecimal(currency.getActiveTrade().getAmount()) + " to " + balance.getFree()); - currency.getActiveTrade().setAmount(amount); - } - } - break; - } - } - } - } -} diff --git a/src/main/java/modes/Simulation.java b/src/main/java/modes/Simulation.java deleted file mode 100644 index c3edd94..0000000 --- a/src/main/java/modes/Simulation.java +++ /dev/null @@ -1,52 +0,0 @@ -package modes; - -import com.binance.api.client.exception.BinanceApiException; -import system.ConfigSetup; -import trading.LocalAccount; -import trading.Currency; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public final class Simulation { - public static double STARTING_VALUE; - private static final List currencies = new ArrayList<>(); - private static LocalAccount localAccount; - - private Simulation() { - throw new IllegalStateException("Utility class"); - } - - public static List getCurrencies() { - return currencies; - } - - public static LocalAccount getAccount() { - return localAccount; - } - - public static void close() { - for (Currency currency : currencies) { - try { - currency.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - public static void init() { - localAccount = new LocalAccount("Investor Toomas", STARTING_VALUE); - - for (String arg : ConfigSetup.getCurrencies()) { - //The currency class contains all of the method calls that drive the activity of our bot - try { - currencies.add(new Currency(arg, getAccount())); - } catch (BinanceApiException e) { - System.out.println("---Could not add " + arg + ConfigSetup.getFiat()); - System.out.println(e.getMessage()); - } - } - } -} diff --git a/src/main/java/trading/BinanceAPI.java b/src/main/java/system/BinanceAPI.java similarity index 97% rename from src/main/java/trading/BinanceAPI.java rename to src/main/java/system/BinanceAPI.java index 24c1dc6..2296ec2 100644 --- a/src/main/java/trading/BinanceAPI.java +++ b/src/main/java/system/BinanceAPI.java @@ -1,4 +1,4 @@ -package trading; +package system; import com.binance.api.client.BinanceApiClientFactory; import com.binance.api.client.BinanceApiRestClient; diff --git a/src/main/java/modes/Collection.java b/src/main/java/system/Collection.java similarity index 99% rename from src/main/java/modes/Collection.java rename to src/main/java/system/Collection.java index e9f6a7a..83cf081 100644 --- a/src/main/java/modes/Collection.java +++ b/src/main/java/system/Collection.java @@ -1,4 +1,4 @@ -package modes; +package system; import com.binance.api.client.BinanceApiAsyncRestClient; import com.binance.api.client.BinanceApiCallback; @@ -10,9 +10,6 @@ import data.PriceReader; import data.PriceWriter; import org.apache.commons.io.FileUtils; -import system.ConfigSetup; -import trading.BinanceAPI; -import system.Formatter; import java.io.*; import java.nio.file.Files; diff --git a/src/main/java/system/ConfigSetup.java b/src/main/java/system/ConfigSetup.java index 5c1dbe4..e34c540 100644 --- a/src/main/java/system/ConfigSetup.java +++ b/src/main/java/system/ConfigSetup.java @@ -4,7 +4,6 @@ import com.binance.api.client.domain.general.RateLimitType; import indicators.MACD; import indicators.RSI; -import modes.Simulation; import trading.*; import trading.Currency; @@ -81,7 +80,7 @@ public static void readConfig() { RSI.NEGATIVE_MAX = Integer.parseInt(arr[1]); break; case "Simulation mode starting value": - Simulation.STARTING_VALUE = Integer.parseInt(arr[1]); + Instance.STARTING_VALUE = Integer.parseInt(arr[1]); break; case "Currencies to track": currencies = Collections.unmodifiableList(Arrays.asList(arr[1].toUpperCase().split(", "))); diff --git a/src/main/java/system/Main.java b/src/main/java/system/Main.java index cdfe461..1a2cdb3 100644 --- a/src/main/java/system/Main.java +++ b/src/main/java/system/Main.java @@ -1,7 +1,5 @@ package system; -import modes.*; -import modes.Collection; import trading.*; import trading.Currency; @@ -53,173 +51,5 @@ public static void main(String[] args) { "\n" + "Simulation and backtesting do not always reflect live performance\n" + "Make sure you are ready to commit to a strategy before starting LIVE\n"); - boolean returnToModes = false; - while (true) { - Scanner sc = new Scanner(System.in); - while (true) { - try { - //TODO: Change mode selection to single character - System.out.println("Enter bot mode (live, simulation, backtesting, collection)"); - Mode.set(Mode.valueOf(sc.nextLine().toUpperCase())); - break; - } catch (Exception e) { - System.out.println("Invalid mode, try again."); - } - } - System.out.println("\n---Entering " + Mode.get().name().toLowerCase() + " mode"); - - - if (Mode.get() == Mode.COLLECTION) { - Collection.startCollection(); //Init collection mode. - - } else { - LocalAccount localAccount = null; - long startTime = System.nanoTime(); - switch (Mode.get()) { - case LIVE: - Live.init(); //Init live mode. - localAccount = Live.getAccount(); - currencies = Live.getCurrencies(); - break; - case SIMULATION: - Simulation.init(); //Init simulation mode. - currencies = Simulation.getCurrencies(); - localAccount = Simulation.getAccount(); - break; - case BACKTESTING: - Backtesting.startBacktesting(); //Init Backtesting mode. - currencies = Backtesting.getCurrencies(); - localAccount = Backtesting.getAccount(); - break; - } - long endTime = System.nanoTime(); - double time = (endTime - startTime) / 1.e9; - - System.out.println("---" + (Mode.get().equals(Mode.BACKTESTING) ? "Backtesting" : "Setup") + " finished (" + Formatter.formatDecimal(time) + " s)\n"); - while (Mode.get().equals(Mode.BACKTESTING)) { - System.out.println("Type \"quit\" to quit"); - System.out.println("Type \"modes\" to got back to mode selection."); - String s = sc.nextLine(); - if (s.equalsIgnoreCase("quit")) { - System.exit(0); - break; - } else if (s.equalsIgnoreCase("modes")) { - returnToModes = true; - break; - } else { - System.out.println("Type quit to quit"); - System.out.println("Type \"modes\" to got back to mode selection."); - } - } - - assert localAccount != null; - //From this point we only use the main thread to check how the bot is doing - Timer timer = new Timer(); - boolean printing = false; - while (!returnToModes) { - System.out.println("\nCommands: profit, active, history, wallet, currencies, open, close, close all, quit, modes"); - String in = sc.nextLine(); - switch (in) { - case "profit": - System.out.println("\nAccount profit: " + Formatter.formatPercent(localAccount.getProfit()) + "\n"); - break; - case "active": - System.out.println("\nActive trades:"); - for (Trade trade : localAccount.getActiveTrades()) { - System.out.println(trade); - } - System.out.println(" "); - break; - case "secret": - if (!printing) { - timer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - System.out.println(currencies.get(0)); - } - }, 0, 100); - printing = true; - } else { - timer.cancel(); - timer.purge(); - printing = false; - } - break; - case "history": - System.out.println("\nClosed trades:"); - for (Trade trade : localAccount.getTradeHistory()) { - System.out.println(trade); - } - break; - case "wallet": - System.out.println("\nTotal wallet value: " + Formatter.formatDecimal(localAccount.getTotalValue()) + " " + ConfigSetup.getFiat()); - System.out.println(Formatter.formatDecimal(localAccount.getFiat()) + " " + ConfigSetup.getFiat()); - for (Map.Entry entry : localAccount.getWallet().entrySet()) { - if (entry.getValue() != 0) { - System.out.println(Formatter.formatDecimal(entry.getValue()) + " " + entry.getKey().getPair().replace(ConfigSetup.getFiat(), "") - + " (" + Formatter.formatDecimal(entry.getKey().getPrice() * entry.getValue()) + " " + ConfigSetup.getFiat() + ")"); - } - } - break; - case "currencies": - for (Currency currency : currencies) { - System.out.println((currencies.indexOf(currency) + 1) + " " + currency); - } - System.out.println(" "); - break; - case "open": - System.out.println("Enter ID of currency"); - String openId = sc.nextLine(); - if (!openId.matches("\\d+")) { - System.out.println("\nNot an integer!"); - continue; - } - int openIndex = Integer.parseInt(openId); - if (openIndex < 1 || openIndex > currencies.size()) { - System.out.println("\nID out of range, use \"currencies\" to see valid IDs!"); - continue; - } - localAccount.open(currencies.get(openIndex - 1), "Trade opened due to: Manually opened\t"); - break; - case "close": - System.out.println("Enter ID of active trade"); - String closeId = sc.nextLine(); - if (!closeId.matches("\\d+")) { - System.out.println("\nNot an integer!"); - continue; - } - int closeIndex = Integer.parseInt(closeId); - if (closeIndex < 1 || closeIndex > currencies.size()) { - System.out.println("\nID out of range, use \"active\" to see valid IDs!"); - continue; - } - localAccount.close(localAccount.getActiveTrades().get(closeIndex - 1)); - break; - case "close all": - localAccount.getActiveTrades().forEach(localAccount::close); - break; - case "refresh": - if (Mode.get().equals(Mode.LIVE)) { - Live.refreshWalletAndTrades(); - System.out.println("---Refreshed wallet and trades"); - } else { - System.out.println("---Can only refresh wallet and trades in live mode!"); - } - break; - case "quit": - System.exit(0); - break; - case "modes": - returnToModes = true; - break; - default: - break; - } - } - timer.cancel(); - Mode.reset(); - returnToModes = false; - } - } } } \ No newline at end of file diff --git a/src/main/java/system/Mode.java b/src/main/java/system/Mode.java deleted file mode 100644 index 5e909e9..0000000 --- a/src/main/java/system/Mode.java +++ /dev/null @@ -1,31 +0,0 @@ -package system; - -import modes.Live; -import modes.Simulation; - - -public enum Mode { - LIVE, - SIMULATION, - BACKTESTING, - COLLECTION; - - private static Mode state; - - public static Mode get() { - return state; - } - - public static void reset() { - if (state.equals(Mode.BACKTESTING) || state.equals(Mode.COLLECTION)) return; - if (state.equals(Mode.SIMULATION)) { - Simulation.close(); - } else { - Live.close(); - } - } - - static void set(Mode state) { - Mode.state = state; - } -} diff --git a/src/main/java/trading/Currency.java b/src/main/java/trading/Currency.java index 80cb571..67bcb09 100644 --- a/src/main/java/trading/Currency.java +++ b/src/main/java/trading/Currency.java @@ -9,9 +9,9 @@ import indicators.Indicator; import indicators.MACD; import indicators.RSI; +import system.BinanceAPI; import system.ConfigSetup; import system.Formatter; -import system.Mode; import java.io.Closeable; import java.io.File; @@ -122,7 +122,7 @@ private void accept(PriceBean bean) { if (bean.isClosing()) { indicators.forEach(indicator -> indicator.update(bean.getPrice())); - if (Mode.get().equals(Mode.BACKTESTING)) { + if (account.getInstance().getMode().equals(Instance.Mode.BACKTESTING)) { appendLogLine(system.Formatter.formatDate(currentTime) + " " + this); } } @@ -272,7 +272,6 @@ public boolean equals(Object obj) { @Override public void close() throws IOException { - if (Mode.get().equals(Mode.BACKTESTING) || Mode.get().equals(Mode.COLLECTION)) return; - apiListener.close(); + if (apiListener != null) apiListener.close(); } } diff --git a/src/main/java/trading/Instance.java b/src/main/java/trading/Instance.java new file mode 100644 index 0000000..f820990 --- /dev/null +++ b/src/main/java/trading/Instance.java @@ -0,0 +1,368 @@ +package trading; + +import com.binance.api.client.domain.account.AssetBalance; +import com.binance.api.client.domain.general.FilterType; +import com.binance.api.client.domain.general.SymbolFilter; +import com.binance.api.client.exception.BinanceApiException; +import system.BinanceAPI; +import system.ConfigSetup; +import system.Formatter; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.util.*; + +public class Instance implements Closeable { + public static double STARTING_VALUE; + private static final File credentialsFile = new File("credentials.txt"); + + private final String ID; + private final LocalAccount account; + private final Mode mode; + + public String getID() { + return ID; + } + + public LocalAccount getAccount() { + return account; + } + + public Mode getMode() { + return mode; + } + + List currencies = new ArrayList<>(); + + @Override + public void close() { + for (Currency currency : currencies) { + try { + currency.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public Instance(List coins, String ID) { + this.ID = ID; + this.mode = Mode.SIMULATION; + this.account = new LocalAccount(this, STARTING_VALUE); + + for (String arg : coins) { + //The currency class contains all of the method calls that drive the activity of our bot + try { + currencies.add(new Currency(arg, account)); + } catch (BinanceApiException e) { + System.out.println("---Could not add " + arg + ConfigSetup.getFiat()); + System.out.println(e.getMessage()); + } + } + } + + //LIVE + public Instance(List coins, String ID, String apiKey, String secretKey) { + this.ID = ID; + this.mode = Mode.LIVE; + + /*boolean fileFailed = true; + if (credentialsFile.exists()) { + try { + final List strings = Files.readAllLines(credentialsFile.toPath()); + if (!strings.get(0).matches("\\*+")) { + account = new LocalAccount(strings.get(0), strings.get(1)); + fileFailed = false; + } else { + System.out.println("---credentials.txt has not been set up"); + } + } catch (Exception e) { + e.printStackTrace(); + System.out.println("---Failed to use credentials in credentials.txt"); + } + } else { + System.out.println("---credentials.txt file not detected!"); + } + + if (fileFailed) { + Scanner sc = new Scanner(System.in); + String apiKey; + String apiSecret; + while (true) { + System.out.println("Enter your API Key: "); + apiKey = sc.nextLine(); + if (apiKey.length() == 64) { + System.out.println("Enter your Secret Key: "); + apiSecret = sc.nextLine(); + if (apiSecret.length() == 64) { + break; + } else System.out.println("Secret API is incorrect, enter again."); + } else System.out.println("Incorrect API, enter again."); + } + }*/ + account = new LocalAccount(this, apiKey, secretKey); + System.out.println("Can trade: " + account.getRealAccount().isCanTrade()); + System.out.println(account.getMakerComission() + " Maker commission."); + System.out.println(account.getBuyerComission() + " Buyer commission"); + System.out.println(account.getTakerComission() + " Taker comission"); + + //TODO: Open price for existing currencies + String current = ""; + try { + List addedCurrencies = new ArrayList<>(); + for (AssetBalance balance : account.getRealAccount().getBalances()) { + if (balance.getFree().matches("0\\.0+")) continue; + if (coins.contains(balance.getAsset())) { + current = balance.getAsset(); + Currency balanceCurrency = new Currency(current, account); + currencies.add(balanceCurrency); + addedCurrencies.add(current); + double amount = Double.parseDouble(balance.getFree()); + account.getWallet().put(balanceCurrency, amount); + double price = Double.parseDouble(BinanceAPI.get().getPrice(current + ConfigSetup.getFiat()).getPrice()); + Optional lotSize = BinanceAPI.get().getExchangeInfo().getSymbolInfo(current + ConfigSetup.getFiat()).getFilters().stream().filter(f -> FilterType.LOT_SIZE == f.getFilterType()).findFirst().map(f1 -> f1.getMinQty()); + Optional minNotational = BinanceAPI.get().getExchangeInfo().getSymbolInfo(current + ConfigSetup.getFiat()).getFilters().stream().filter(f -> FilterType.MIN_NOTIONAL == f.getFilterType()).findFirst().map(SymbolFilter::getMinNotional); + if (lotSize.isPresent()) { + if (amount < Double.parseDouble(lotSize.get())) { + System.out.println(balance.getFree() + " " + current + " is less than LOT_SIZE " + lotSize.get()); + continue; + } + } + if (minNotational.isPresent()) { + if (amount * price < Double.parseDouble(minNotational.get())) { + System.out.println(current + " notational value of " + + Formatter.formatDecimal(amount * price) + " is less than min notational " + + minNotational.get()); + continue; + } + } + final Trade trade = new Trade(balanceCurrency, balanceCurrency.getPrice(), amount, "Trade opened due to: Added based on live account\t"); + account.getActiveTrades().add(trade); + balanceCurrency.setActiveTrade(trade); + System.out.println("Added an active trade of " + balance.getFree() + " " + current + " at " + Formatter.formatDecimal(trade.getEntryPrice()) + " based on existing balance in account"); + } + } + account.setStartingValue(account.getTotalValue()); + for (String arg : coins) { + if (!addedCurrencies.contains(arg)) { + current = arg; + currencies.add(new Currency(current, account)); + } + } + } catch (Exception e) { + System.out.println("---Could not add " + current + ConfigSetup.getFiat()); + System.out.println(e.getMessage()); + } + } + + //BACKTESTING + public Instance(String path) { + this.ID = path; + this.account = new LocalAccount(this, STARTING_VALUE); + this.mode = Mode.BACKTESTING; + + /* + final String[] backtestingFiles = Collection.getDataFiles(); + if (backtestingFiles.length == 0) { + System.out.println("No backtesting files detected!"); + System.exit(0); + } + Scanner sc = new Scanner(System.in); + System.out.println("\nBacktesting data files:\n"); + for (int i = 0; i < backtestingFiles.length; i++) { + System.out.println("[" + (i + 1) + "] " + backtestingFiles[i]); + } + System.out.println("\nEnter a number to select the backtesting data file"); + String input = sc.nextLine(); + if (!input.matches("\\d+")) continue; + int index = Integer.parseInt(input); + if (index > backtestingFiles.length) { + + } + String path = "backtesting/" + backtestingFiles[index - 1]; + */ + + System.out.println("\n---Backtesting..."); + Currency currency = new Currency(new File(path).getName().split("_")[0], path, account); + currencies.add(currency); + + for (Trade trade : account.getActiveTrades()) { + trade.setExplanation(trade.getExplanation() + "Manually closed"); + account.close(trade); + } + + + int i = 1; + path = path.replace("backtesting", "log"); + String resultPath = path.replace(".dat", "_run_" + i + ".txt"); + while (new File(resultPath).exists()) { + i++; + resultPath = path.replace(".dat", "_run_" + i + ".txt"); + } + new File("log").mkdir(); + + currency.log(resultPath); + } + + public void refreshWalletAndTrades() { + if (mode != Mode.LIVE) return; + for (AssetBalance balance : account.getRealAccount().getBalances()) { + if (balance.getFree().matches("0\\.0+")) continue; + if (balance.getAsset().equals(ConfigSetup.getFiat())) { + final double amount = Double.parseDouble(balance.getFree()); + if (account.getFiat() != amount) { + System.out.println("---Refreshed " + balance.getAsset() + " from " + Formatter.formatDecimal(account.getFiat()) + " to " + amount); + System.out.println(balance.getLocked()); + account.setFiat(amount); + } + continue; + } + for (Currency currency : currencies) { + if ((balance.getAsset() + ConfigSetup.getFiat()).equals(currency.getPair())) { + final double amount = Double.parseDouble(balance.getFree()); + if (!account.getWallet().containsKey(currency)) { + System.out.println("---Refreshed " + currency.getPair() + " from 0 to " + balance.getFree()); + account.getWallet().replace(currency, amount); + } + if (account.getWallet().get(currency) != amount) { + System.out.println("---Refreshed " + currency.getPair() + " from " + Formatter.formatDecimal(account.getWallet().get(currency)) + " to " + balance.getFree()); + System.out.println(balance.getLocked()); + account.getWallet().replace(currency, amount); + } + if (currency.hasActiveTrade()) { + if (currency.getActiveTrade().getAmount() > amount) { + System.out.println("---Refreshed " + currency.getPair() + " trade from " + Formatter.formatDecimal(currency.getActiveTrade().getAmount()) + " to " + balance.getFree()); + currency.getActiveTrade().setAmount(amount); + } + } + break; + } + } + } + System.out.println("---Refreshed wallet and trades"); + } + + static void Interface(Instance instance) { + Scanner sc = new Scanner(System.in); + assert instance.account != null; + //From this point we only use the main thread to check how the bot is doing + boolean printing = false; + boolean exitInterface = false; + Timer timer = null; + while (!exitInterface) { + System.out.println("\nCommands: profit, active, history, wallet, currencies, open, close, close all, stop, modes"); + String in = sc.nextLine(); + switch (in) { + case "profit": + System.out.println("\nAccount profit: " + Formatter.formatPercent(instance.account.getProfit()) + "\n"); + break; + case "active": + System.out.println("\nActive trades:"); + for (Trade trade : instance.account.getActiveTrades()) { + System.out.println(trade); + } + System.out.println(" "); + break; + case "secret": + if (!printing) { + timer = new Timer(); + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + System.out.println(instance.currencies.get(0)); + } + }, 0, 100); + printing = true; + } else { + timer.cancel(); + timer.purge(); + printing = false; + } + break; + case "history": + System.out.println("\nClosed trades:"); + for (Trade trade : instance.account.getTradeHistory()) { + System.out.println(trade); + } + break; + case "wallet": + System.out.println("\nTotal wallet value: " + Formatter.formatDecimal(instance.account.getTotalValue()) + " " + ConfigSetup.getFiat()); + System.out.println(Formatter.formatDecimal(instance.account.getFiat()) + " " + ConfigSetup.getFiat()); + for (Map.Entry entry : instance.account.getWallet().entrySet()) { + if (entry.getValue() != 0) { + System.out.println(Formatter.formatDecimal(entry.getValue()) + " " + entry.getKey().getPair().replace(ConfigSetup.getFiat(), "") + + " (" + Formatter.formatDecimal(entry.getKey().getPrice() * entry.getValue()) + " " + ConfigSetup.getFiat() + ")"); + } + } + break; + case "currencies": + for (Currency currency : instance.currencies) { + System.out.println((instance.currencies.indexOf(currency) + 1) + " " + currency); + } + System.out.println(" "); + break; + case "open": + System.out.println("Enter ID of currency"); + String openId = sc.nextLine(); + if (!openId.matches("\\d+")) { + System.out.println("\nNot an integer!"); + continue; + } + int openIndex = Integer.parseInt(openId); + if (openIndex < 1 || openIndex > instance.currencies.size()) { + System.out.println("\nID out of range, use \"currencies\" to see valid IDs!"); + continue; + } + instance.account.open(instance.currencies.get(openIndex - 1), "Trade opened due to: Manually opened\t"); + break; + case "close": + System.out.println("Enter ID of active trade"); + String closeId = sc.nextLine(); + if (!closeId.matches("\\d+")) { + System.out.println("\nNot an integer!"); + continue; + } + int closeIndex = Integer.parseInt(closeId); + if (closeIndex < 1 || closeIndex > instance.currencies.size()) { + System.out.println("\nID out of range, use \"active\" to see valid IDs!"); + continue; + } + instance.account.close(instance.account.getActiveTrades().get(closeIndex - 1)); + break; + case "close all": + instance.account.getActiveTrades().forEach(instance.account::close); + break; + case "refresh": + instance.refreshWalletAndTrades(); + break; + case "stop": + System.out.println("Close all open trades? (y/n)"); + String answer = sc.nextLine(); + answer = answer.trim(); + if (answer.equalsIgnoreCase("y")) { + instance.account.getActiveTrades().forEach(instance.account::close); + } else if (!answer.equalsIgnoreCase("n")) { + return; + } + instance.close(); + exitInterface = true; + break; + case "return": + exitInterface = true; + break; + default: + break; + } + } + if (timer != null) { + timer.cancel(); + } + } + + public enum Mode { + LIVE, + SIMULATION, + BACKTESTING; + } +} diff --git a/src/main/java/trading/LocalAccount.java b/src/main/java/trading/LocalAccount.java index ef67a61..eb0a94e 100644 --- a/src/main/java/trading/LocalAccount.java +++ b/src/main/java/trading/LocalAccount.java @@ -9,9 +9,9 @@ import com.binance.api.client.domain.general.FilterType; import com.binance.api.client.domain.general.SymbolFilter; import com.binance.api.client.exception.BinanceApiException; +import system.BinanceAPI; import system.ConfigSetup; import system.Formatter; -import system.Mode; import java.math.BigDecimal; import java.math.RoundingMode; @@ -28,7 +28,7 @@ public class LocalAccount { public static double MONEY_PER_TRADE; - private final String username; + private final Instance instance; private Account realAccount; private BinanceApiClientFactory apiFactory; private BinanceApiRestClient client; @@ -47,8 +47,8 @@ public class LocalAccount { * Wallet value will most probably be 0 at first, but you could start * with an existing wallet value as well. */ - public LocalAccount(String username, double startingValue) { - this.username = username; + public LocalAccount(Instance instance, double startingValue) { + this.instance = instance; this.startingValue = startingValue; fiatValue = startingValue; wallet = new ConcurrentHashMap<>(); @@ -56,11 +56,11 @@ public LocalAccount(String username, double startingValue) { activeTrades = new CopyOnWriteArrayList<>(); } - public LocalAccount(String apiKey, String secretApiKey) { + public LocalAccount(Instance instance, String apiKey, String secretApiKey) { + this.instance = instance; apiFactory = BinanceAPI.login(apiKey, secretApiKey); client = apiFactory.newRestClient(); - username = ""; wallet = new ConcurrentHashMap<>(); tradeHistory = new ArrayList<>(); activeTrades = new CopyOnWriteArrayList<>(); @@ -110,15 +110,14 @@ public void closeTrade(Trade trade) { tradeHistory.add(trade); } - //All the get methods. - public String getUsername() { - return username; - } - public double getFiat() { return fiatValue; } + public Instance getInstance() { + return instance; + } + public void setFiat(double fiatValue) { this.fiatValue = fiatValue; } @@ -213,7 +212,7 @@ public void open(Currency currency, String explanation) { double amount = fiatCost / currency.getPrice(); Trade trade; - if (Mode.get().equals(Mode.LIVE)) { + if (instance.getMode().equals(Instance.Mode.LIVE)) { NewOrderResponse order = placeOrder(currency, amount, true); if (order == null) { return; @@ -251,12 +250,12 @@ public void open(Currency currency, String explanation) { + currency.getPair() + "), at " + Formatter.formatDecimal(trade.getEntryPrice()) + ", " + trade.getExplanation(); System.out.println(message); - if (Mode.get().equals(Mode.BACKTESTING)) currency.appendLogLine(message); + if (instance.getMode().equals(Instance.Mode.BACKTESTING)) currency.appendLogLine(message); } //Used by trade public void close(Trade trade) { - if (Mode.get().equals(Mode.LIVE)) { + if (instance.getMode().equals(Instance.Mode.LIVE)) { NewOrderResponse order = placeOrder(trade.getCurrency(), trade.getAmount(), false); if (order == null) { return; @@ -295,11 +294,11 @@ public void close(Trade trade) { + ", with " + Formatter.formatPercent(trade.getProfit()) + " profit" + "\n------" + trade.getExplanation(); System.out.println(message); - if (Mode.get().equals(Mode.BACKTESTING)) trade.getCurrency().appendLogLine(message); + if (instance.getMode().equals(Instance.Mode.BACKTESTING)) trade.getCurrency().appendLogLine(message); } private double nextAmount() { - if (Mode.get().equals(Mode.BACKTESTING)) return getFiat(); + if (instance.getMode().equals(Instance.Mode.BACKTESTING)) return getFiat(); return Math.min(getFiat(), getTotalValue() * MONEY_PER_TRADE); } From 279e2075d170ff7857134f863a3da92c81fcaa76 Mon Sep 17 00:00:00 2001 From: Markus Aksli Date: Sat, 17 Apr 2021 16:44:12 +0300 Subject: [PATCH 3/8] Minified Main.java --- src/main/java/system/Main.java | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/src/main/java/system/Main.java b/src/main/java/system/Main.java index 1a2cdb3..d93ce27 100644 --- a/src/main/java/system/Main.java +++ b/src/main/java/system/Main.java @@ -1,14 +1,9 @@ package system; -import trading.*; -import trading.Currency; - import java.util.*; public class Main { - private static List currencies; - public static void main(String[] args) { //Program config. try { @@ -25,31 +20,6 @@ public static void main(String[] args) { new Scanner(System.in).next(); System.exit(3); } - System.out.println("Welcome to TradeBot (v0.10.0)\n" + - "(made by Markus Aksli, Marten Türk, and Mark Robin Kalder)\n" + - "\n" + - "This is a cryptocurrency trading bot that uses the Binance API,\n" + - "and a strategy based on a couple of 5 minute chart indicators\n" + - "(RSI, MACD, Bollinger Bands)\n" + - "\n" + - "The bot has the following modes of operation:\n" + - "---LIVE\n" + - "-This mode trades with real money on the Binance platform\n" + - "-API key and Secret key required\n" + - "---SIMULATION\n" + - "-Real-time trading simulation based on actual market data\n" + - "-Trades are only simulated based on market prices \n" + - "-No actual orders are made\n" + - "---BACKTESTING\n" + - "-Simulation based on historical data.\n" + - "-Allows for quick testing of the behavior and profitability of the bot\n" + - "-Data needs to be loaded from a .dat file created with the COLLECTION mode\n" + - "---COLLECTION\n" + - "-Collects raw market price data from a specified time period\n" + - "-Collected data is saved in a file in the /backtesting directory\n" + - "-Never run more than one TradeBot with this mode at the same time\n" + - "\n" + - "Simulation and backtesting do not always reflect live performance\n" + - "Make sure you are ready to commit to a strategy before starting LIVE\n"); + System.out.println("Welcome to TradeBot (v0.11.0)"); } } \ No newline at end of file From 3477690d5ea8e8b7505a194a6cee9938fe0e4714 Mon Sep 17 00:00:00 2001 From: Markus Aksli Date: Sun, 9 May 2021 03:30:45 +0300 Subject: [PATCH 4/8] Added yaml config, new config data system --- config.txt | 14 -- config.yaml | 23 ++++ pom.xml | 10 ++ src/main/java/data/config/ConfigData.java | 90 +++++++++++++ src/main/java/data/config/DbbData.java | 28 ++++ src/main/java/data/config/IndicatorData.java | 24 ++++ src/main/java/data/config/MacdData.java | 58 ++++++++ src/main/java/data/config/RsiData.java | 72 ++++++++++ src/main/java/data/{ => price}/PriceBean.java | 2 +- .../java/data/{ => price}/PriceReader.java | 2 +- .../java/data/{ => price}/PriceWriter.java | 2 +- src/main/java/system/Collection.java | 18 +-- src/main/java/system/Config.java | 80 +++++++++++ src/main/java/system/ConfigSetup.java | 125 ------------------ src/main/java/system/Main.java | 5 +- src/main/java/trading/Currency.java | 9 +- src/main/java/trading/Instance.java | 41 +++--- src/main/java/trading/LocalAccount.java | 42 +++--- 18 files changed, 449 insertions(+), 196 deletions(-) delete mode 100644 config.txt create mode 100644 config.yaml create mode 100644 src/main/java/data/config/ConfigData.java create mode 100644 src/main/java/data/config/DbbData.java create mode 100644 src/main/java/data/config/IndicatorData.java create mode 100644 src/main/java/data/config/MacdData.java create mode 100644 src/main/java/data/config/RsiData.java rename src/main/java/data/{ => price}/PriceBean.java (98%) rename src/main/java/data/{ => price}/PriceReader.java (96%) rename src/main/java/data/{ => price}/PriceWriter.java (96%) create mode 100644 src/main/java/system/Config.java delete mode 100644 src/main/java/system/ConfigSetup.java diff --git a/config.txt b/config.txt deleted file mode 100644 index 829de08..0000000 --- a/config.txt +++ /dev/null @@ -1,14 +0,0 @@ -MACD change indicator:0.25 -RSI positive side minimum:15 -RSI positive side maximum:30 -RSI negative side minimum:70 -RSI negative side maximum:80 -Simulation mode starting value:10000 -Percentage of money per trade:0.2 -Trailing SL:0.1 -Take profit:0.15 -Confluence:2 -Close confluence:2 -Use confluence to close:true -Currencies to track:BTC, ETH -FIAT:EUR \ No newline at end of file diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..e6da953 --- /dev/null +++ b/config.yaml @@ -0,0 +1,23 @@ +moneyPerTrade: 0.1 +trailingSl: 0.1 +takeProfit: 0.15 + +confluenceToOpen: 2 +#confluenceToClose: 2 +indicators: + - ! + weight: 1 + period: 14 + positiveMax: 15 + positiveMin: 30 + negativeMax: 70 + negativeMin: 80 + - ! + weight: 1 + shortPeriod: 12 + longPeriod: 26 + signalPeriod: 9 + requiredChange: 0.15 + - ! + weight: 1 + period: 20 diff --git a/pom.xml b/pom.xml index 1d57f5d..9917a1b 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,16 @@ + + com.fasterxml.jackson.core + jackson-databind + 2.12.3 + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + 2.12.3 + com.github.binance-exchange binance-java-api diff --git a/src/main/java/data/config/ConfigData.java b/src/main/java/data/config/ConfigData.java new file mode 100644 index 0000000..148f15d --- /dev/null +++ b/src/main/java/data/config/ConfigData.java @@ -0,0 +1,90 @@ +package data.config; + +import java.util.List; + +public class ConfigData { + private double moneyPerTrade; + private double trailingSl; + private double takeProfit; + private int confluenceToOpen; + private Integer confluenceToClose; + + private List indicators; + + public ConfigData(double moneyPerTrade, double trailingSl, double takeProfit, int confluenceToOpen, Integer confluenceToClose, List indicators) { + this.moneyPerTrade = moneyPerTrade; + this.trailingSl = trailingSl; + this.takeProfit = takeProfit; + this.confluenceToOpen = confluenceToOpen; + this.confluenceToClose = confluenceToClose; + this.indicators = indicators; + } + + public ConfigData() { + } + + + public double getMoneyPerTrade() { + return moneyPerTrade; + } + + public void setMoneyPerTrade(double moneyPerTrade) { + this.moneyPerTrade = moneyPerTrade; + } + + public double getTrailingSl() { + return trailingSl; + } + + public void setTrailingSl(double trailingSl) { + this.trailingSl = trailingSl; + } + + public double getTakeProfit() { + return takeProfit; + } + + public void setTakeProfit(double takeProfit) { + this.takeProfit = takeProfit; + } + + public int getConfluenceToOpen() { + return confluenceToOpen; + } + + public void setConfluenceToOpen(int confluenceToOpen) { + this.confluenceToOpen = confluenceToOpen; + } + + public Integer getConfluenceToClose() { + return confluenceToClose; + } + + public void setConfluenceToClose(Integer confluenceToClose) { + this.confluenceToClose = confluenceToClose; + } + + public List getIndicators() { + return indicators; + } + + public void setIndicators(List indicators) { + this.indicators = indicators; + } + + public boolean useConfluenceToClose() { + return confluenceToClose != null; + } + + @Override + public String toString() { + return "ConfigData{" + + "indicators=" + indicators + + ", moneyPerTrade=" + moneyPerTrade + + ", trailingSl=" + trailingSl + + ", takeProfit=" + takeProfit + + ", confluenceToOpen=" + confluenceToOpen + + ", confluenceToClose=" + confluenceToClose + + '}'; + } +} diff --git a/src/main/java/data/config/DbbData.java b/src/main/java/data/config/DbbData.java new file mode 100644 index 0000000..a6fcd82 --- /dev/null +++ b/src/main/java/data/config/DbbData.java @@ -0,0 +1,28 @@ +package data.config; + +public class DbbData extends IndicatorData { + private int period; + + public DbbData(int weight, int period) { + setWeight(weight); + this.period = period; + } + + public DbbData() { + } + + public int getPeriod() { + return period; + } + + public void setPeriod(int period) { + this.period = period; + } + + @Override + public String toString() { + return "DbbData{" + + "weight=" + getWeight() + + '}'; + } +} diff --git a/src/main/java/data/config/IndicatorData.java b/src/main/java/data/config/IndicatorData.java new file mode 100644 index 0000000..cc4e029 --- /dev/null +++ b/src/main/java/data/config/IndicatorData.java @@ -0,0 +1,24 @@ +package data.config; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + property = "name") +@JsonSubTypes({ + @JsonSubTypes.Type(value = RsiData.class, name = "RSI"), + @JsonSubTypes.Type(value = MacdData.class, name = "MACD"), + @JsonSubTypes.Type(value = DbbData.class, name = "DBB") +}) +public abstract class IndicatorData { + private int weight; + + public int getWeight() { + return weight; + } + + public void setWeight(int weight) { + this.weight = weight; + } +} diff --git a/src/main/java/data/config/MacdData.java b/src/main/java/data/config/MacdData.java new file mode 100644 index 0000000..b264acc --- /dev/null +++ b/src/main/java/data/config/MacdData.java @@ -0,0 +1,58 @@ +package data.config; + +public class MacdData extends IndicatorData { + private int shortPeriod; + private int longPeriod; + private int signalPeriod; + private double requiredChange; + + public MacdData(int weight, int shortPeriod, int longPeriod, int signalPeriod, double requiredChange) { + this.setWeight(weight); + this.shortPeriod = shortPeriod; + this.longPeriod = longPeriod; + this.signalPeriod = signalPeriod; + this.requiredChange = requiredChange; + } + + public MacdData() { + } + + public int getShortPeriod() { + return shortPeriod; + } + + public void setShortPeriod(int shortPeriod) { + this.shortPeriod = shortPeriod; + } + + public int getLongPeriod() { + return longPeriod; + } + + public void setLongPeriod(int longPeriod) { + this.longPeriod = longPeriod; + } + + public int getSignalPeriod() { + return signalPeriod; + } + + public void setSignalPeriod(int signalPeriod) { + this.signalPeriod = signalPeriod; + } + + public double getRequiredChange() { + return requiredChange; + } + + public void setRequiredChange(double requiredChange) { + this.requiredChange = requiredChange; + } + + @Override + public String toString() { + return "MacdData{" + + "requiredChange=" + requiredChange + + '}'; + } +} diff --git a/src/main/java/data/config/RsiData.java b/src/main/java/data/config/RsiData.java new file mode 100644 index 0000000..105599c --- /dev/null +++ b/src/main/java/data/config/RsiData.java @@ -0,0 +1,72 @@ +package data.config; + +public class RsiData extends IndicatorData { + private int period; + private int positiveMax; + private int positiveMin; + private int negativeMax; + private int negativeMin; + + public RsiData(int weight, int period, int positiveMax, int positiveMin, int negativeMax, int negativeMin) { + this.setWeight(weight); + this.period = period; + this.positiveMax = positiveMax; + this.positiveMin = positiveMin; + this.negativeMax = negativeMax; + this.negativeMin = negativeMin; + } + + public RsiData() { + } + + public int getPeriod() { + return period; + } + + public void setPeriod(int period) { + this.period = period; + } + + public int getPositiveMax() { + return positiveMax; + } + + public void setPositiveMax(int positiveMax) { + this.positiveMax = positiveMax; + } + + public int getPositiveMin() { + return positiveMin; + } + + public void setPositiveMin(int positiveMin) { + this.positiveMin = positiveMin; + } + + public int getNegativeMax() { + return negativeMax; + } + + public void setNegativeMax(int negativeMax) { + this.negativeMax = negativeMax; + } + + public int getNegativeMin() { + return negativeMin; + } + + public void setNegativeMin(int negativeMin) { + this.negativeMin = negativeMin; + } + + @Override + public String toString() { + return "RSIData{" + + "weight=" + getWeight() + + ", positiveMax=" + positiveMax + + ", positiveMin=" + positiveMin + + ", negativeMax=" + negativeMax + + ", negativeMin=" + negativeMin + + '}'; + } +} diff --git a/src/main/java/data/PriceBean.java b/src/main/java/data/price/PriceBean.java similarity index 98% rename from src/main/java/data/PriceBean.java rename to src/main/java/data/price/PriceBean.java index 3d1eade..6fd6150 100644 --- a/src/main/java/data/PriceBean.java +++ b/src/main/java/data/price/PriceBean.java @@ -1,4 +1,4 @@ -package data; +package data.price; import system.Formatter; diff --git a/src/main/java/data/PriceReader.java b/src/main/java/data/price/PriceReader.java similarity index 96% rename from src/main/java/data/PriceReader.java rename to src/main/java/data/price/PriceReader.java index eb6bcf5..9f4b668 100644 --- a/src/main/java/data/PriceReader.java +++ b/src/main/java/data/price/PriceReader.java @@ -1,4 +1,4 @@ -package data; +package data.price; import java.io.*; diff --git a/src/main/java/data/PriceWriter.java b/src/main/java/data/price/PriceWriter.java similarity index 96% rename from src/main/java/data/PriceWriter.java rename to src/main/java/data/price/PriceWriter.java index d0cd83a..3cc8de6 100644 --- a/src/main/java/data/PriceWriter.java +++ b/src/main/java/data/price/PriceWriter.java @@ -1,4 +1,4 @@ -package data; +package data.price; import java.io.*; diff --git a/src/main/java/system/Collection.java b/src/main/java/system/Collection.java index 83cf081..0146534 100644 --- a/src/main/java/system/Collection.java +++ b/src/main/java/system/Collection.java @@ -6,9 +6,9 @@ import com.binance.api.client.domain.market.Candlestick; import com.binance.api.client.domain.market.CandlestickInterval; import com.binance.api.client.exception.BinanceApiException; -import data.PriceBean; -import data.PriceReader; -import data.PriceWriter; +import data.price.PriceBean; +import data.price.PriceReader; +import data.price.PriceWriter; import org.apache.commons.io.FileUtils; import java.io.*; @@ -185,10 +185,10 @@ public static void startCollection() { if (!returnToModes) { return; } - System.out.println("Enter collectable currency (BTC, LINK, ETH...)"); + System.out.println("Enter collectable currency pair (BTCUSDT, LINKBTC...)"); while (true) { try { - symbol = sc.nextLine().toUpperCase() + ConfigSetup.getFiat(); + symbol = sc.nextLine().toUpperCase(); BinanceAPI.get().getPrice(symbol); break; } catch (BinanceApiException e) { @@ -243,9 +243,9 @@ public static void startCollection() { e.printStackTrace(); } - int requestDelay = 60000 / ConfigSetup.getRequestLimit(); - System.out.println("---Request delay: " + requestDelay + " ms (" + ConfigSetup.getRequestLimit() + " per minute)"); - System.out.println("---Sending " + chunks + " requests (minimum estimate is " + (Formatter.formatDuration((long) ((double) chunks / (double) ConfigSetup.getRequestLimit() * 60000L)) + ")...")); + int requestDelay = 60000 / Config.getRequestLimit(); + System.out.println("---Request delay: " + requestDelay + " ms (" + Config.getRequestLimit() + " per minute)"); + System.out.println("---Sending " + chunks + " requests (minimum estimate is " + (Formatter.formatDuration((long) ((double) chunks / (double) Config.getRequestLimit() * 60000L)) + ")...")); initTime = System.currentTimeMillis(); Timer timer = new Timer(); timer.scheduleAtFixedRate(new TimerTask() { @@ -417,7 +417,7 @@ public static void checkBacktestingData(String filename) { } if (bean.getTimestamp() - last > 1800000L && !bean.isClosing()) { if (firstGap) { - System.out.println("-Gaps (checking for 30min+) usually point to exchange maintenance times, check https://www.binance.com/en/trade/pro/" + symbol.replace(ConfigSetup.getFiat(), "_" + ConfigSetup.getFiat()) + " if suspicious"); + System.out.println("-Gaps (checking for 30min+) usually point to exchange maintenance times, check https://www.binance.com/ if suspicious"); firstGap = false; } System.out.println("Gap from " + Formatter.formatDate(last) + " to " + Formatter.formatDate(bean.getTimestamp())); diff --git a/src/main/java/system/Config.java b/src/main/java/system/Config.java new file mode 100644 index 0000000..328dd8b --- /dev/null +++ b/src/main/java/system/Config.java @@ -0,0 +1,80 @@ +package system; + +import com.binance.api.client.domain.general.RateLimit; +import com.binance.api.client.domain.general.RateLimitType; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import data.config.*; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +public class Config { + private static final int REQUEST_LIMIT = BinanceAPI.get().getExchangeInfo().getRateLimits().stream() + .filter(rateLimit -> rateLimit.getRateLimitType().equals(RateLimitType.REQUEST_WEIGHT)) + .findFirst().map(RateLimit::getLimit).orElse(1200); + + public static void main(String[] args) { + File file = new File("config.yaml"); + ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()); + Set set = new HashSet<>(); + ConfigData crazyConfig = new ConfigData( + 0.1, + 0.1, + 0.15, + 2, + null, + Arrays.asList(new RsiData(1, 14, 15, 30, 70, 80), new MacdData(1, 12, 26, 9, 0.15), new DbbData(1, 20)) + ); + try { + System.out.println(objectMapper.writeValueAsString(crazyConfig)); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + + ConfigData config = null; + try { + config = objectMapper.readValue(file, ConfigData.class); + } catch (IOException e) { + e.printStackTrace(); + } + System.out.println(config.toString()); + } + + private final File configFile; + private ConfigData data; + + public Config(String path) throws ConfigException { + configFile = new File(path); + readValues(); + } + + public static int getRequestLimit() { + return REQUEST_LIMIT; + } + + public ConfigData getData() { + return data; + } + + public void readValues() throws ConfigException { + ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()); + try { + data = objectMapper.readValue(configFile, ConfigData.class); + } catch (IOException e) { + throw new ConfigException("Failed to read config file due to: " + e.getMessage()); + } + } + + @Override + public String toString() { + ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()); + try { + return objectMapper.writeValueAsString(data); + } catch (JsonProcessingException e) { + return "Failed to serialize config: " + e.getMessage(); + } + } +} diff --git a/src/main/java/system/ConfigSetup.java b/src/main/java/system/ConfigSetup.java deleted file mode 100644 index e34c540..0000000 --- a/src/main/java/system/ConfigSetup.java +++ /dev/null @@ -1,125 +0,0 @@ -package system; - -import com.binance.api.client.domain.general.RateLimit; -import com.binance.api.client.domain.general.RateLimitType; -import indicators.MACD; -import indicators.RSI; -import trading.*; -import trading.Currency; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.util.*; - -public class ConfigSetup { - private static final int REQUEST_LIMIT = BinanceAPI.get().getExchangeInfo().getRateLimits().stream() - .filter(rateLimit -> rateLimit.getRateLimitType().equals(RateLimitType.REQUEST_WEIGHT)) - .findFirst().map(RateLimit::getLimit).orElse(1200); - - private static final StringBuilder setup = new StringBuilder(); - private static List currencies; - private static String fiat; - - public ConfigSetup() { - throw new IllegalStateException("Utility class"); - } - - public static String getSetup() { - return setup.toString(); - } - - public static List getCurrencies() { - return currencies; - } - - public static int getRequestLimit() { - return REQUEST_LIMIT; - } - - public static String getFiat() { - return fiat; - } - - public static void readConfig() { - Formatter.getSimpleFormatter().setTimeZone(TimeZone.getDefault()); - int items = 0; - File file = new File("config.txt"); - if (!file.exists()) { - System.out.println("No config file detected!"); - new Scanner(System.in).nextLine(); - System.exit(1); - } - try (FileReader reader = new FileReader(file); - BufferedReader br = new BufferedReader(reader)) { - String line; - while ((line = br.readLine()) != null) { - if (!line.isBlank() && !line.isEmpty()) { - setup.append(line).append("\n"); - } else { - continue; - } - String[] arr = line.strip().split(":"); - if (arr.length != 2) continue; - items++; - switch (arr[0]) { - case "MACD change indicator": - MACD.SIGNAL_CHANGE = (Double.parseDouble(arr[1])); - break; - case "RSI positive side minimum": - RSI.POSITIVE_MIN = Integer.parseInt(arr[1]); - break; - case "RSI positive side maximum": - RSI.POSITIVE_MAX = Integer.parseInt(arr[1]); - break; - case "RSI negative side minimum": - RSI.NEGATIVE_MIN = Integer.parseInt(arr[1]); - break; - case "RSI negative side maximum": - RSI.NEGATIVE_MAX = Integer.parseInt(arr[1]); - break; - case "Simulation mode starting value": - Instance.STARTING_VALUE = Integer.parseInt(arr[1]); - break; - case "Currencies to track": - currencies = Collections.unmodifiableList(Arrays.asList(arr[1].toUpperCase().split(", "))); - break; - case "Percentage of money per trade": - LocalAccount.MONEY_PER_TRADE = Double.parseDouble(arr[1]); - break; - case "Trailing SL": - Trade.TRAILING_SL = Double.parseDouble(arr[1]); - break; - case "Take profit": - Trade.TAKE_PROFIT = Double.parseDouble(arr[1]); - break; - case "Confluence": - Currency.CONFLUENCE_TARGET = Integer.parseInt(arr[1]); - break; - case "Close confluence": - Trade.CLOSE_CONFLUENCE = Integer.parseInt(arr[1]); - break; - case "Use confluence to close": - Trade.CLOSE_USE_CONFLUENCE = Boolean.parseBoolean(arr[1]); - break; - case "FIAT": - fiat = arr[1].toUpperCase(); - break; - default: - items--; - break; - } - } - if (items < 12) { //12 is the number of configuration elements in the file. - throw new ConfigException("Config file has some missing elements."); - } - - } catch (IOException e) { - e.printStackTrace(); - } catch (ConfigException e) { - e.printStackTrace(); - System.exit(0); - } - } -} diff --git a/src/main/java/system/Main.java b/src/main/java/system/Main.java index d93ce27..40030c2 100644 --- a/src/main/java/system/Main.java +++ b/src/main/java/system/Main.java @@ -5,9 +5,10 @@ public class Main { public static void main(String[] args) { + Formatter.getSimpleFormatter().setTimeZone(TimeZone.getDefault()); //Program config. try { - ConfigSetup.readConfig(); + BinanceAPI.get().getPrice("BTCUSDT"); } catch (ExceptionInInitializerError cause) { if (cause.getCause() != null) { if (cause.getCause().getMessage() != null && cause.getCause().getMessage().toLowerCase().contains("banned")) { @@ -21,5 +22,7 @@ public static void main(String[] args) { System.exit(3); } System.out.println("Welcome to TradeBot (v0.11.0)"); + + //TODO: Implement CLI interface to create and monitor instances } } \ No newline at end of file diff --git a/src/main/java/trading/Currency.java b/src/main/java/trading/Currency.java index 67bcb09..5946841 100644 --- a/src/main/java/trading/Currency.java +++ b/src/main/java/trading/Currency.java @@ -3,14 +3,13 @@ import com.binance.api.client.BinanceApiWebSocketClient; import com.binance.api.client.domain.market.Candlestick; import com.binance.api.client.domain.market.CandlestickInterval; -import data.PriceBean; -import data.PriceReader; +import data.price.PriceBean; +import data.price.PriceReader; import indicators.DBB; import indicators.Indicator; import indicators.MACD; import indicators.RSI; import system.BinanceAPI; -import system.ConfigSetup; import system.Formatter; import java.io.Closeable; @@ -47,7 +46,7 @@ public class Currency implements Closeable { //Used for SIMULATION and LIVE public Currency(String coin, LocalAccount account) { - this.pair = coin + ConfigSetup.getFiat(); + this.pair = coin + account.getInstance().getFiat(); this.account = account; //Every currency needs to contain and update our indicators @@ -194,7 +193,7 @@ public void log(String path) { try (FileWriter writer = new FileWriter(path)) { writer.write("Test ended " + system.Formatter.formatDate(LocalDateTime.now()) + " \n"); writer.write("\n\nCONFIG:\n"); - writer.write(ConfigSetup.getSetup()); + writer.write(account.getInstance().getConfig().toString()); writer.write("\n\nMarket performance: " + system.Formatter.formatPercent((currentPrice - firstBean.getPrice()) / firstBean.getPrice())); if (!tradeHistory.isEmpty()) { tradeHistory.sort(Comparator.comparingDouble(Trade::getProfit)); diff --git a/src/main/java/trading/Instance.java b/src/main/java/trading/Instance.java index f820990..c1f842a 100644 --- a/src/main/java/trading/Instance.java +++ b/src/main/java/trading/Instance.java @@ -4,8 +4,9 @@ import com.binance.api.client.domain.general.FilterType; import com.binance.api.client.domain.general.SymbolFilter; import com.binance.api.client.exception.BinanceApiException; +import data.config.ConfigData; import system.BinanceAPI; -import system.ConfigSetup; +import system.Config; import system.Formatter; import java.io.Closeable; @@ -20,6 +21,8 @@ public class Instance implements Closeable { private final String ID; private final LocalAccount account; private final Mode mode; + private String fiat; + private Config config; public String getID() { return ID; @@ -56,7 +59,7 @@ public Instance(List coins, String ID) { try { currencies.add(new Currency(arg, account)); } catch (BinanceApiException e) { - System.out.println("---Could not add " + arg + ConfigSetup.getFiat()); + System.out.println("---Could not add " + arg + fiat); System.out.println(e.getMessage()); } } @@ -103,9 +106,9 @@ public Instance(List coins, String ID, String apiKey, String secretKey) }*/ account = new LocalAccount(this, apiKey, secretKey); System.out.println("Can trade: " + account.getRealAccount().isCanTrade()); - System.out.println(account.getMakerComission() + " Maker commission."); - System.out.println(account.getBuyerComission() + " Buyer commission"); - System.out.println(account.getTakerComission() + " Taker comission"); + System.out.println(account.getMakerCommission() + " Maker commission."); + System.out.println(account.getBuyerCommission() + " Buyer commission"); + System.out.println(account.getTakerCommission() + " Taker comission"); //TODO: Open price for existing currencies String current = ""; @@ -120,9 +123,9 @@ public Instance(List coins, String ID, String apiKey, String secretKey) addedCurrencies.add(current); double amount = Double.parseDouble(balance.getFree()); account.getWallet().put(balanceCurrency, amount); - double price = Double.parseDouble(BinanceAPI.get().getPrice(current + ConfigSetup.getFiat()).getPrice()); - Optional lotSize = BinanceAPI.get().getExchangeInfo().getSymbolInfo(current + ConfigSetup.getFiat()).getFilters().stream().filter(f -> FilterType.LOT_SIZE == f.getFilterType()).findFirst().map(f1 -> f1.getMinQty()); - Optional minNotational = BinanceAPI.get().getExchangeInfo().getSymbolInfo(current + ConfigSetup.getFiat()).getFilters().stream().filter(f -> FilterType.MIN_NOTIONAL == f.getFilterType()).findFirst().map(SymbolFilter::getMinNotional); + double price = Double.parseDouble(BinanceAPI.get().getPrice(current + fiat).getPrice()); + Optional lotSize = BinanceAPI.get().getExchangeInfo().getSymbolInfo(current + fiat).getFilters().stream().filter(f -> FilterType.LOT_SIZE == f.getFilterType()).findFirst().map(f1 -> f1.getMinQty()); + Optional minNotational = BinanceAPI.get().getExchangeInfo().getSymbolInfo(current + fiat).getFilters().stream().filter(f -> FilterType.MIN_NOTIONAL == f.getFilterType()).findFirst().map(SymbolFilter::getMinNotional); if (lotSize.isPresent()) { if (amount < Double.parseDouble(lotSize.get())) { System.out.println(balance.getFree() + " " + current + " is less than LOT_SIZE " + lotSize.get()); @@ -151,7 +154,7 @@ public Instance(List coins, String ID, String apiKey, String secretKey) } } } catch (Exception e) { - System.out.println("---Could not add " + current + ConfigSetup.getFiat()); + System.out.println("---Could not add " + current + fiat); System.out.println(e.getMessage()); } } @@ -209,7 +212,7 @@ public void refreshWalletAndTrades() { if (mode != Mode.LIVE) return; for (AssetBalance balance : account.getRealAccount().getBalances()) { if (balance.getFree().matches("0\\.0+")) continue; - if (balance.getAsset().equals(ConfigSetup.getFiat())) { + if (balance.getAsset().equals(fiat)) { final double amount = Double.parseDouble(balance.getFree()); if (account.getFiat() != amount) { System.out.println("---Refreshed " + balance.getAsset() + " from " + Formatter.formatDecimal(account.getFiat()) + " to " + amount); @@ -219,7 +222,7 @@ public void refreshWalletAndTrades() { continue; } for (Currency currency : currencies) { - if ((balance.getAsset() + ConfigSetup.getFiat()).equals(currency.getPair())) { + if ((balance.getAsset() + fiat).equals(currency.getPair())) { final double amount = Double.parseDouble(balance.getFree()); if (!account.getWallet().containsKey(currency)) { System.out.println("---Refreshed " + currency.getPair() + " from 0 to " + balance.getFree()); @@ -287,12 +290,12 @@ public void run() { } break; case "wallet": - System.out.println("\nTotal wallet value: " + Formatter.formatDecimal(instance.account.getTotalValue()) + " " + ConfigSetup.getFiat()); - System.out.println(Formatter.formatDecimal(instance.account.getFiat()) + " " + ConfigSetup.getFiat()); + System.out.println("\nTotal wallet value: " + Formatter.formatDecimal(instance.account.getTotalValue()) + " " + instance.getFiat()); + System.out.println(Formatter.formatDecimal(instance.account.getFiat()) + " " + instance.getFiat()); for (Map.Entry entry : instance.account.getWallet().entrySet()) { if (entry.getValue() != 0) { - System.out.println(Formatter.formatDecimal(entry.getValue()) + " " + entry.getKey().getPair().replace(ConfigSetup.getFiat(), "") - + " (" + Formatter.formatDecimal(entry.getKey().getPrice() * entry.getValue()) + " " + ConfigSetup.getFiat() + ")"); + System.out.println(Formatter.formatDecimal(entry.getValue()) + " " + entry.getKey().getPair().replace(instance.getFiat(), "") + + " (" + Formatter.formatDecimal(entry.getKey().getPrice() * entry.getValue()) + " " + instance.getFiat() + ")"); } } break; @@ -360,6 +363,14 @@ public void run() { } } + public ConfigData getConfig() { + return config.getData(); + } + + public String getFiat() { + return fiat; + } + public enum Mode { LIVE, SIMULATION, diff --git a/src/main/java/trading/LocalAccount.java b/src/main/java/trading/LocalAccount.java index eb0a94e..9b01613 100644 --- a/src/main/java/trading/LocalAccount.java +++ b/src/main/java/trading/LocalAccount.java @@ -1,6 +1,5 @@ package trading; -import com.binance.api.client.BinanceApiClientFactory; import com.binance.api.client.BinanceApiRestClient; import com.binance.api.client.domain.OrderStatus; import com.binance.api.client.domain.account.Account; @@ -10,7 +9,6 @@ import com.binance.api.client.domain.general.SymbolFilter; import com.binance.api.client.exception.BinanceApiException; import system.BinanceAPI; -import system.ConfigSetup; import system.Formatter; import java.math.BigDecimal; @@ -26,11 +24,8 @@ import static com.binance.api.client.domain.account.NewOrder.marketSell; public class LocalAccount { - public static double MONEY_PER_TRADE; - private final Instance instance; private Account realAccount; - private BinanceApiClientFactory apiFactory; private BinanceApiRestClient client; //To give the account a specific final amount of money. @@ -39,9 +34,9 @@ public class LocalAccount { private final ConcurrentHashMap wallet; private final List tradeHistory; private final List activeTrades; - private double makerComission; - private double takerComission; - private double buyerComission; + private double makerCommission; + private double takerCommission; + private double buyerCommission; /** * Wallet value will most probably be 0 at first, but you could start @@ -58,8 +53,7 @@ public LocalAccount(Instance instance, double startingValue) { public LocalAccount(Instance instance, String apiKey, String secretApiKey) { this.instance = instance; - apiFactory = BinanceAPI.login(apiKey, secretApiKey); - client = apiFactory.newRestClient(); + client = BinanceAPI.login(apiKey, secretApiKey).newRestClient(); wallet = new ConcurrentHashMap<>(); tradeHistory = new ArrayList<>(); @@ -68,20 +62,20 @@ public LocalAccount(Instance instance, String apiKey, String secretApiKey) { if (!realAccount.isCanTrade()) { System.out.println("Can't trade!"); } - makerComission = realAccount.getMakerCommission(); //Maker fees are + makerCommission = realAccount.getMakerCommission(); //Maker fees are // paid when you add liquidity to our order book // by placing a limit order below the ticker price for buy, and above the ticker price for sell. - takerComission = realAccount.getTakerCommission();//Taker fees are paid when you remove + takerCommission = realAccount.getTakerCommission();//Taker fees are paid when you remove // liquidity from our order book by placing any order that is executed against an order on the order book. - buyerComission = realAccount.getBuyerCommission(); + buyerCommission = realAccount.getBuyerCommission(); //Example: If the current market/ticker price is $2000 for 1 BTC and you market buy bitcoins starting at the market price of $2000, then you will pay the taker fee. In this instance, you have taken liquidity/coins from the order book. // //If the current market/ticker price is $2000 for 1 BTC and you //place a limit buy for bitcoins at $1995, then //you will pay the maker fee IF the market/ticker price moves into your limit order at $1995. - fiatValue = Double.parseDouble(realAccount.getAssetBalance(ConfigSetup.getFiat()).getFree()); - System.out.println("---Starting FIAT: " + Formatter.formatDecimal(fiatValue) + " " + ConfigSetup.getFiat()); + fiatValue = Double.parseDouble(realAccount.getAssetBalance(instance.getFiat()).getFree()); + System.out.println("---Starting FIAT: " + Formatter.formatDecimal(fiatValue) + " " + instance.getFiat()); } public Account getRealAccount() { @@ -180,16 +174,16 @@ public void removeFromWallet(Currency key, double value) { wallet.put(key, wallet.get(key) - value); } - public double getMakerComission() { - return makerComission; + public double getMakerCommission() { + return makerCommission; } - public double getTakerComission() { - return takerComission; + public double getTakerCommission() { + return takerCommission; } - public double getBuyerComission() { - return buyerComission; + public double getBuyerCommission() { + return buyerCommission; } public boolean enoughFunds() { @@ -227,7 +221,7 @@ public void open(Currency currency, String explanation) { } System.out.println("Got filled for " + BigDecimal.valueOf(fillsQty).toString() + " at " + Formatter.formatDate(order.getTransactTime()) - + ", at a price of " + Formatter.formatDecimal(fillsPrice) + " " + ConfigSetup.getFiat()); + + ", at a price of " + Formatter.formatDecimal(fillsPrice) + " " + instance.getFiat()); fiatCost = fillsPrice; amount = fillsQty; trade = new Trade(currency, fillsPrice / fillsQty, amount, explanation); @@ -269,7 +263,7 @@ public void close(Trade trade) { } System.out.println("Got filled for " + BigDecimal.valueOf(fillsQty).toString() + " at " + Formatter.formatDate(order.getTransactTime()) - + ", at a price of " + Formatter.formatDecimal(fillsPrice) + " " + ConfigSetup.getFiat()); + + ", at a price of " + Formatter.formatDecimal(fillsPrice) + " " + instance.getFiat()); trade.setClosePrice(fillsPrice / fillsQty); trade.setCloseTime(order.getTransactTime()); removeFromWallet(trade.getCurrency(), fillsQty); @@ -299,7 +293,7 @@ public void close(Trade trade) { private double nextAmount() { if (instance.getMode().equals(Instance.Mode.BACKTESTING)) return getFiat(); - return Math.min(getFiat(), getTotalValue() * MONEY_PER_TRADE); + return Math.min(getFiat(), getTotalValue() * instance.getConfig().getMoneyPerTrade()); } From 1a7df69c42e09ba6325e8efb7c8da3101f38308f Mon Sep 17 00:00:00 2001 From: Markus Aksli Date: Mon, 10 May 2021 20:48:12 +0300 Subject: [PATCH 5/8] Fully implemented new config system, removed static variables, added config updating, tested communication with flutter --- config.yaml => configs/example.yaml | 2 +- configs/full.yaml | 24 +++++ .../java/{system => data/config}/Config.java | 59 +++++------- src/main/java/data/config/ConfigData.java | 22 ++++- .../config}/ConfigException.java | 2 +- .../data/config/ConfigUpdateException.java | 7 ++ src/main/java/data/config/DbbConfig.java | 49 ++++++++++ src/main/java/data/config/DbbData.java | 28 ------ .../java/data/config/IndicatorConfig.java | 37 ++++++++ src/main/java/data/config/IndicatorData.java | 24 ----- src/main/java/data/config/MacdConfig.java | 91 +++++++++++++++++++ src/main/java/data/config/MacdData.java | 58 ------------ .../config/{RsiData.java => RsiConfig.java} | 31 ++++++- src/main/java/indicators/DBB.java | 28 +++--- src/main/java/indicators/EMA.java | 32 +++---- src/main/java/indicators/Indicator.java | 2 +- src/main/java/indicators/MACD.java | 40 ++++---- src/main/java/indicators/RSI.java | 42 ++++----- src/main/java/indicators/SMA.java | 18 ++-- src/main/java/system/Collection.java | 1 + src/main/java/system/InstanceEndpoint.java | 27 ++++++ src/main/java/trading/Currency.java | 32 +++---- src/main/java/trading/Instance.java | 2 +- src/main/java/trading/LocalAccount.java | 4 - src/main/java/trading/Trade.java | 17 ++-- 25 files changed, 413 insertions(+), 266 deletions(-) rename config.yaml => configs/example.yaml (94%) create mode 100644 configs/full.yaml rename src/main/java/{system => data/config}/Config.java (56%) rename src/main/java/{system => data/config}/ConfigException.java (85%) create mode 100644 src/main/java/data/config/ConfigUpdateException.java create mode 100644 src/main/java/data/config/DbbConfig.java delete mode 100644 src/main/java/data/config/DbbData.java create mode 100644 src/main/java/data/config/IndicatorConfig.java delete mode 100644 src/main/java/data/config/IndicatorData.java create mode 100644 src/main/java/data/config/MacdConfig.java delete mode 100644 src/main/java/data/config/MacdData.java rename src/main/java/data/config/{RsiData.java => RsiConfig.java} (58%) create mode 100644 src/main/java/system/InstanceEndpoint.java diff --git a/config.yaml b/configs/example.yaml similarity index 94% rename from config.yaml rename to configs/example.yaml index e6da953..a549712 100644 --- a/config.yaml +++ b/configs/example.yaml @@ -3,7 +3,7 @@ trailingSl: 0.1 takeProfit: 0.15 confluenceToOpen: 2 -#confluenceToClose: 2 +confluenceToClose: 2 indicators: - ! weight: 1 diff --git a/configs/full.yaml b/configs/full.yaml new file mode 100644 index 0000000..35cad10 --- /dev/null +++ b/configs/full.yaml @@ -0,0 +1,24 @@ +#TODO: Everything should be documented in here +moneyPerTrade: 0.1 +trailingSl: 0.1 +takeProfit: 0.15 + +confluenceToOpen: 2 +confluenceToClose: 2 +indicators: + - ! + weight: 1 + period: 14 + positiveMax: 15 + positiveMin: 30 + negativeMax: 70 + negativeMin: 80 + - ! + weight: 1 + shortPeriod: 12 + longPeriod: 26 + signalPeriod: 9 + requiredChange: 0.15 + - ! + weight: 1 + period: 20 diff --git a/src/main/java/system/Config.java b/src/main/java/data/config/Config.java similarity index 56% rename from src/main/java/system/Config.java rename to src/main/java/data/config/Config.java index 328dd8b..bb05f14 100644 --- a/src/main/java/system/Config.java +++ b/src/main/java/data/config/Config.java @@ -1,54 +1,41 @@ -package system; +package data.config; import com.binance.api.client.domain.general.RateLimit; import com.binance.api.client.domain.general.RateLimitType; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import data.config.*; +import system.BinanceAPI; +import trading.Currency; +import trading.LocalAccount; +import trading.Trade; import java.io.File; import java.io.IOException; -import java.util.*; public class Config { private static final int REQUEST_LIMIT = BinanceAPI.get().getExchangeInfo().getRateLimits().stream() .filter(rateLimit -> rateLimit.getRateLimitType().equals(RateLimitType.REQUEST_WEIGHT)) .findFirst().map(RateLimit::getLimit).orElse(1200); - public static void main(String[] args) { - File file = new File("config.yaml"); - ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()); - Set set = new HashSet<>(); - ConfigData crazyConfig = new ConfigData( - 0.1, - 0.1, - 0.15, - 2, - null, - Arrays.asList(new RsiData(1, 14, 15, 30, 70, 80), new MacdData(1, 12, 26, 9, 0.15), new DbbData(1, 20)) - ); - try { - System.out.println(objectMapper.writeValueAsString(crazyConfig)); - } catch (JsonProcessingException e) { - e.printStackTrace(); - } - - ConfigData config = null; - try { - config = objectMapper.readValue(file, ConfigData.class); - } catch (IOException e) { - e.printStackTrace(); - } - System.out.println(config.toString()); - } - private final File configFile; - private ConfigData data; + private final ConfigData data; public Config(String path) throws ConfigException { configFile = new File(path); - readValues(); + data = readValues(); + } + + public static ConfigData get(Trade trade) { + return trade.getCurrency().getAccount().getInstance().getConfig(); + } + + public static ConfigData get(Currency currency) { + return currency.getAccount().getInstance().getConfig(); + } + + public static ConfigData get(LocalAccount account) { + return account.getInstance().getConfig(); } public static int getRequestLimit() { @@ -59,15 +46,19 @@ public ConfigData getData() { return data; } - public void readValues() throws ConfigException { + public ConfigData readValues() throws ConfigException { ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()); try { - data = objectMapper.readValue(configFile, ConfigData.class); + return objectMapper.readValue(configFile, ConfigData.class); } catch (IOException e) { throw new ConfigException("Failed to read config file due to: " + e.getMessage()); } } + public void update() throws ConfigException { + data.update(readValues()); + } + @Override public String toString() { ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()); diff --git a/src/main/java/data/config/ConfigData.java b/src/main/java/data/config/ConfigData.java index 148f15d..89d3ba0 100644 --- a/src/main/java/data/config/ConfigData.java +++ b/src/main/java/data/config/ConfigData.java @@ -7,11 +7,11 @@ public class ConfigData { private double trailingSl; private double takeProfit; private int confluenceToOpen; - private Integer confluenceToClose; + private Integer confluenceToClose; //Number of indicators can't be changed - private List indicators; + private List indicators; - public ConfigData(double moneyPerTrade, double trailingSl, double takeProfit, int confluenceToOpen, Integer confluenceToClose, List indicators) { + public ConfigData(double moneyPerTrade, double trailingSl, double takeProfit, int confluenceToOpen, Integer confluenceToClose, List indicators) { this.moneyPerTrade = moneyPerTrade; this.trailingSl = trailingSl; this.takeProfit = takeProfit; @@ -23,6 +23,18 @@ public ConfigData(double moneyPerTrade, double trailingSl, double takeProfit, in public ConfigData() { } + public void update(ConfigData newConfig) throws ConfigUpdateException { + if (newConfig.getIndicators().size() != indicators.size()) + throw new ConfigUpdateException("Number of indicators has changed"); + for (int i = 0; i < indicators.size(); i++) { + indicators.get(i).update(newConfig.getIndicators().get(i)); + } + moneyPerTrade = newConfig.moneyPerTrade; + trailingSl = newConfig.trailingSl; + takeProfit = newConfig.takeProfit; + confluenceToOpen = newConfig.confluenceToOpen; + confluenceToClose = newConfig.confluenceToClose; + } public double getMoneyPerTrade() { return moneyPerTrade; @@ -64,11 +76,11 @@ public void setConfluenceToClose(Integer confluenceToClose) { this.confluenceToClose = confluenceToClose; } - public List getIndicators() { + public List getIndicators() { return indicators; } - public void setIndicators(List indicators) { + public void setIndicators(List indicators) { this.indicators = indicators; } diff --git a/src/main/java/system/ConfigException.java b/src/main/java/data/config/ConfigException.java similarity index 85% rename from src/main/java/system/ConfigException.java rename to src/main/java/data/config/ConfigException.java index f35b885..6045a7d 100644 --- a/src/main/java/system/ConfigException.java +++ b/src/main/java/data/config/ConfigException.java @@ -1,4 +1,4 @@ -package system; +package data.config; public class ConfigException extends Exception { public ConfigException(String message) { diff --git a/src/main/java/data/config/ConfigUpdateException.java b/src/main/java/data/config/ConfigUpdateException.java new file mode 100644 index 0000000..17dc63b --- /dev/null +++ b/src/main/java/data/config/ConfigUpdateException.java @@ -0,0 +1,7 @@ +package data.config; + +public class ConfigUpdateException extends ConfigException { + public ConfigUpdateException(String message) { + super("Failed to update config: " + message); + } +} diff --git a/src/main/java/data/config/DbbConfig.java b/src/main/java/data/config/DbbConfig.java new file mode 100644 index 0000000..2bd2d26 --- /dev/null +++ b/src/main/java/data/config/DbbConfig.java @@ -0,0 +1,49 @@ +package data.config; + +import indicators.DBB; +import indicators.Indicator; + +import java.util.List; + +public class DbbConfig extends IndicatorConfig { + private int period; + + public DbbConfig(int weight, int period) { + setWeight(weight); + this.period = period; + } + + public DbbConfig() { + } + + public int getPeriod() { + return period; + } + + public void setPeriod(int period) { + this.period = period; + } + + @Override + public Indicator toIndicator(List warmupData) { + return new DBB(warmupData, this); + } + + @Override + public void update(IndicatorConfig newConfig) throws ConfigUpdateException { + super.update(newConfig); + DbbConfig newDbbConfig = (DbbConfig) newConfig; + if (newDbbConfig.getPeriod() != period) { + throw new ConfigUpdateException("DBB period has changed from " + period + " to " + newDbbConfig.period + + ". Period cannot be changed because DBB values are affected by the size of the rolling window."); + } + } + + @Override + public String toString() { + return "DbbData{" + + "weight=" + getWeight() + + "period=" + period + + '}'; + } +} diff --git a/src/main/java/data/config/DbbData.java b/src/main/java/data/config/DbbData.java deleted file mode 100644 index a6fcd82..0000000 --- a/src/main/java/data/config/DbbData.java +++ /dev/null @@ -1,28 +0,0 @@ -package data.config; - -public class DbbData extends IndicatorData { - private int period; - - public DbbData(int weight, int period) { - setWeight(weight); - this.period = period; - } - - public DbbData() { - } - - public int getPeriod() { - return period; - } - - public void setPeriod(int period) { - this.period = period; - } - - @Override - public String toString() { - return "DbbData{" + - "weight=" + getWeight() + - '}'; - } -} diff --git a/src/main/java/data/config/IndicatorConfig.java b/src/main/java/data/config/IndicatorConfig.java new file mode 100644 index 0000000..539ac71 --- /dev/null +++ b/src/main/java/data/config/IndicatorConfig.java @@ -0,0 +1,37 @@ +package data.config; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import indicators.Indicator; + +import java.util.List; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + property = "name") +@JsonSubTypes({ + @JsonSubTypes.Type(value = RsiConfig.class, name = "RSI"), + @JsonSubTypes.Type(value = MacdConfig.class, name = "MACD"), + @JsonSubTypes.Type(value = DbbConfig.class, name = "DBB") +}) +public abstract class IndicatorConfig { + private int weight; + + public abstract Indicator toIndicator(List warmupData); + + //Subclasses must call super if overriding + public void update(IndicatorConfig newConfig) throws ConfigUpdateException { + if (newConfig.getClass() != getClass()) throw new ConfigUpdateException("Indicator order has changed"); + weight = newConfig.weight; + } + + public int getWeight() { + return weight; + } + + public void setWeight(int weight) { + + System.out.println(getClass()); + this.weight = weight; + } +} diff --git a/src/main/java/data/config/IndicatorData.java b/src/main/java/data/config/IndicatorData.java deleted file mode 100644 index cc4e029..0000000 --- a/src/main/java/data/config/IndicatorData.java +++ /dev/null @@ -1,24 +0,0 @@ -package data.config; - -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; - -@JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - property = "name") -@JsonSubTypes({ - @JsonSubTypes.Type(value = RsiData.class, name = "RSI"), - @JsonSubTypes.Type(value = MacdData.class, name = "MACD"), - @JsonSubTypes.Type(value = DbbData.class, name = "DBB") -}) -public abstract class IndicatorData { - private int weight; - - public int getWeight() { - return weight; - } - - public void setWeight(int weight) { - this.weight = weight; - } -} diff --git a/src/main/java/data/config/MacdConfig.java b/src/main/java/data/config/MacdConfig.java new file mode 100644 index 0000000..3594fb7 --- /dev/null +++ b/src/main/java/data/config/MacdConfig.java @@ -0,0 +1,91 @@ +package data.config; + +import indicators.Indicator; +import indicators.MACD; + +import java.util.List; + +public class MacdConfig extends IndicatorConfig { + private int shortPeriod; + private int longPeriod; + private int signalPeriod; + private double requiredChange; + + public MacdConfig(int weight, int shortPeriod, int longPeriod, int signalPeriod, double requiredChange) { + this.setWeight(weight); + this.shortPeriod = shortPeriod; + this.longPeriod = longPeriod; + this.signalPeriod = signalPeriod; + this.requiredChange = requiredChange; + } + + public MacdConfig() { + } + + public int getShortPeriod() { + return shortPeriod; + } + + public void setShortPeriod(int shortPeriod) { + this.shortPeriod = shortPeriod; + } + + public int getLongPeriod() { + return longPeriod; + } + + public void setLongPeriod(int longPeriod) { + this.longPeriod = longPeriod; + } + + public int getSignalPeriod() { + return signalPeriod; + } + + public void setSignalPeriod(int signalPeriod) { + this.signalPeriod = signalPeriod; + } + + public double getRequiredChange() { + return requiredChange; + } + + public void setRequiredChange(double requiredChange) { + this.requiredChange = requiredChange; + } + + @Override + public Indicator toIndicator(List warmupData) { + return new MACD(warmupData, this); + } + + @Override + public void update(IndicatorConfig newConfig) throws ConfigUpdateException { + super.update(newConfig); + MacdConfig newMacdConfig = (MacdConfig) newConfig; + if (newMacdConfig.longPeriod != longPeriod) { + throw new ConfigUpdateException("MACD long period has changed from " + longPeriod + " to " + newMacdConfig.longPeriod + + ". Period cannot be changed because exponential indicators are affeced by history."); + } + if (newMacdConfig.shortPeriod != shortPeriod) { + throw new ConfigUpdateException("MACD short period has changed from " + shortPeriod + " to " + newMacdConfig.shortPeriod + + ". Period cannot be changed because exponential indicators are affeced by history."); + } + if (newMacdConfig.signalPeriod != signalPeriod) { + throw new ConfigUpdateException("MACD signal period has changed from " + signalPeriod + " to " + newMacdConfig.signalPeriod + + ". Period cannot be changed because exponential indicators are affeced by history."); + } + requiredChange = newMacdConfig.requiredChange; + } + + @Override + public String toString() { + return "MacdConfig{" + + "weight=" + getWeight() + + ", shortPeriod=" + shortPeriod + + ", longPeriod=" + longPeriod + + ", signalPeriod=" + signalPeriod + + ", requiredChange=" + requiredChange + + '}'; + } +} diff --git a/src/main/java/data/config/MacdData.java b/src/main/java/data/config/MacdData.java deleted file mode 100644 index b264acc..0000000 --- a/src/main/java/data/config/MacdData.java +++ /dev/null @@ -1,58 +0,0 @@ -package data.config; - -public class MacdData extends IndicatorData { - private int shortPeriod; - private int longPeriod; - private int signalPeriod; - private double requiredChange; - - public MacdData(int weight, int shortPeriod, int longPeriod, int signalPeriod, double requiredChange) { - this.setWeight(weight); - this.shortPeriod = shortPeriod; - this.longPeriod = longPeriod; - this.signalPeriod = signalPeriod; - this.requiredChange = requiredChange; - } - - public MacdData() { - } - - public int getShortPeriod() { - return shortPeriod; - } - - public void setShortPeriod(int shortPeriod) { - this.shortPeriod = shortPeriod; - } - - public int getLongPeriod() { - return longPeriod; - } - - public void setLongPeriod(int longPeriod) { - this.longPeriod = longPeriod; - } - - public int getSignalPeriod() { - return signalPeriod; - } - - public void setSignalPeriod(int signalPeriod) { - this.signalPeriod = signalPeriod; - } - - public double getRequiredChange() { - return requiredChange; - } - - public void setRequiredChange(double requiredChange) { - this.requiredChange = requiredChange; - } - - @Override - public String toString() { - return "MacdData{" + - "requiredChange=" + requiredChange + - '}'; - } -} diff --git a/src/main/java/data/config/RsiData.java b/src/main/java/data/config/RsiConfig.java similarity index 58% rename from src/main/java/data/config/RsiData.java rename to src/main/java/data/config/RsiConfig.java index 105599c..90bfa45 100644 --- a/src/main/java/data/config/RsiData.java +++ b/src/main/java/data/config/RsiConfig.java @@ -1,13 +1,18 @@ package data.config; -public class RsiData extends IndicatorData { +import indicators.Indicator; +import indicators.RSI; + +import java.util.List; + +public class RsiConfig extends IndicatorConfig { private int period; private int positiveMax; private int positiveMin; private int negativeMax; private int negativeMin; - public RsiData(int weight, int period, int positiveMax, int positiveMin, int negativeMax, int negativeMin) { + public RsiConfig(int weight, int period, int positiveMax, int positiveMin, int negativeMax, int negativeMin) { this.setWeight(weight); this.period = period; this.positiveMax = positiveMax; @@ -16,7 +21,7 @@ public RsiData(int weight, int period, int positiveMax, int positiveMin, int neg this.negativeMin = negativeMin; } - public RsiData() { + public RsiConfig() { } public int getPeriod() { @@ -59,10 +64,30 @@ public void setNegativeMin(int negativeMin) { this.negativeMin = negativeMin; } + @Override + public Indicator toIndicator(List warmupData) { + return new RSI(warmupData, this); + } + + @Override + public void update(IndicatorConfig newConfig) throws ConfigUpdateException { + super.update(newConfig); + RsiConfig newRsiConfig = (RsiConfig) newConfig; + if (newRsiConfig.period != period) { + throw new ConfigUpdateException("RSI period has changed from " + period + " to " + newRsiConfig.period + + ". Period cannot be changed because exponential indicators are affeced by history."); + } + positiveMax = newRsiConfig.positiveMax; + positiveMin = newRsiConfig.positiveMin; + negativeMax = newRsiConfig.negativeMax; + negativeMin = newRsiConfig.negativeMin; + } + @Override public String toString() { return "RSIData{" + "weight=" + getWeight() + + ", period=" + period + ", positiveMax=" + positiveMax + ", positiveMin=" + positiveMin + ", negativeMax=" + negativeMax + diff --git a/src/main/java/indicators/DBB.java b/src/main/java/indicators/DBB.java index 6f204dd..ea50cfd 100644 --- a/src/main/java/indicators/DBB.java +++ b/src/main/java/indicators/DBB.java @@ -1,10 +1,14 @@ package indicators; +import data.config.DbbConfig; + import java.util.List; +//Common period for BB is 20 public class DBB implements Indicator { + private final DbbConfig config; + private double closingPrice; private double standardDeviation; - private final int period; private double upperBand; private double upperMidBand; private double middleBand; @@ -13,10 +17,10 @@ public class DBB implements Indicator { private String explanation; private SMA sma; - public DBB(List closingPrices, int period) { - this.period = period; - this.sma = new SMA(closingPrices, period); - init(closingPrices); + public DBB(List warmupData, DbbConfig config) { + this.config = config; + this.sma = new SMA(warmupData, config.getPeriod()); + init(warmupData); } @Override @@ -50,16 +54,16 @@ public double getTemp(double newPrice) { } @Override - public void init(List closingPrices) { - if (period > closingPrices.size()) return; + public void init(List warmupData) { + if (config.getPeriod() > warmupData.size()) return; - closingPrice = closingPrices.size() - 2; + closingPrice = warmupData.size() - 2; standardDeviation = sma.standardDeviation(); middleBand = sma.get(); - upperBand = middleBand + standardDeviation*2; + upperBand = middleBand + standardDeviation * 2; upperMidBand = middleBand + standardDeviation; lowerMidBand = middleBand - standardDeviation; - lowerBand = middleBand - standardDeviation*2; + lowerBand = middleBand - standardDeviation * 2; } @@ -79,11 +83,11 @@ public void update(double newPrice) { public int check(double newPrice) { if (getTemp(newPrice) == 1) { explanation = "Price in DBB buy zone"; - return 1; + return config.getWeight(); } if (getTemp(newPrice) == -1) { explanation = "Price in DBB sell zone"; - return -1; + return -config.getWeight(); } explanation = ""; return 0; diff --git a/src/main/java/indicators/EMA.java b/src/main/java/indicators/EMA.java index ca0fe62..2b3088e 100644 --- a/src/main/java/indicators/EMA.java +++ b/src/main/java/indicators/EMA.java @@ -7,21 +7,20 @@ * EXPONENTIAL MOVING AVERAGE */ public class EMA implements Indicator { - - private double currentEMA; private final int period; private final double multiplier; - private final List EMAhistory; + private final List history; private final boolean historyNeeded; - private String fileName; - public EMA(List closingPrices, int period, boolean historyNeeded) { + private double currentEMA; + + public EMA(List warmupData, int period, boolean historyNeeded) { currentEMA = 0; this.period = period; this.historyNeeded = historyNeeded; this.multiplier = 2.0 / (double) (period + 1); - this.EMAhistory = new ArrayList<>(); - init(closingPrices); + this.history = new ArrayList<>(); + init(warmupData); } @Override @@ -35,28 +34,27 @@ public double getTemp(double newPrice) { } @Override - public void init(List closingPrices) { - if (period > closingPrices.size()) return; + public void init(List warmupData) { + if (period > warmupData.size()) return; //Initial SMA for (int i = 0; i < period; i++) { - currentEMA += closingPrices.get(i); + currentEMA += warmupData.get(i); } currentEMA = currentEMA / (double) period; - if (historyNeeded) EMAhistory.add(currentEMA); + if (historyNeeded) history.add(currentEMA); //Dont use latest unclosed candle; - for (int i = period; i < closingPrices.size() - 1; i++) { - update(closingPrices.get(i)); + for (int i = period; i < warmupData.size() - 1; i++) { + update(warmupData.get(i)); } } @Override public void update(double newPrice) { - // EMA = (Close - EMA(previousBar)) * multiplier + EMA(previousBar) currentEMA = (newPrice - currentEMA) * multiplier + currentEMA; - if (historyNeeded) EMAhistory.add(currentEMA); + if (historyNeeded) history.add(currentEMA); } @Override @@ -69,8 +67,8 @@ public String getExplanation() { return null; } - public List getEMAhistory() { - return EMAhistory; + public List getHistory() { + return history; } public int getPeriod() { diff --git a/src/main/java/indicators/Indicator.java b/src/main/java/indicators/Indicator.java index 0918c93..0d905c9 100644 --- a/src/main/java/indicators/Indicator.java +++ b/src/main/java/indicators/Indicator.java @@ -11,7 +11,7 @@ public interface Indicator { double getTemp(double newPrice); //Used in constructor to set initial value - void init(List closingPrices); + void init(List warmupData); //Used to update value with latest closed candle closing price void update(double newPrice); diff --git a/src/main/java/indicators/MACD.java b/src/main/java/indicators/MACD.java index 088f816..646e3d2 100644 --- a/src/main/java/indicators/MACD.java +++ b/src/main/java/indicators/MACD.java @@ -1,5 +1,6 @@ package indicators; +import data.config.MacdConfig; import system.Formatter; import java.util.List; @@ -7,27 +8,28 @@ //Default setting in crypto are period of 9, short 12 and long 26. //MACD = 12 EMA - 26 EMA and compare to 9 period of MACD value. public class MACD implements Indicator { - - private double currentMACD; - private double currentSignal; + private final MacdConfig config; private final EMA shortEMA; //Will be the EMA object for shortEMA- private final EMA longEMA; //Will be the EMA object for longEMA. private final int period; //Only value that has to be calculated in setInitial. private final double multiplier; private final int periodDifference; + + private double currentMACD; + private double currentSignal; private String explanation; - public static double SIGNAL_CHANGE; private double lastTick; - public MACD(List closingPrices, int shortPeriod, int longPeriod, int signalPeriod) { - this.shortEMA = new EMA(closingPrices, shortPeriod, true); //true, because history is needed in MACD calculations. - this.longEMA = new EMA(closingPrices, longPeriod, true); //true for the same reasons. - this.period = signalPeriod; - this.multiplier = 2.0 / (double) (signalPeriod + 1); - this.periodDifference = longPeriod - shortPeriod; + public MACD(List warmupData, MacdConfig config) { + this.config = config; + this.shortEMA = new EMA(warmupData, config.getShortPeriod(), true); //true, because history is needed in MACD calculations. + this.longEMA = new EMA(warmupData, config.getLongPeriod(), true); //true for the same reasons. + this.period = config.getSignalPeriod(); + this.multiplier = 2.0 / (double) (period + 1); + this.periodDifference = config.getLongPeriod() - config.getShortPeriod(); explanation = ""; - init(closingPrices); //initializing the calculations to get current MACD and signal line. + init(warmupData); //initializing the calculations to get current MACD and signal line. } @Override @@ -47,19 +49,19 @@ public double getTemp(double newPrice) { } @Override - public void init(List closingPrices) { + public void init(List warmupData) { //Initial signal line //i = longEMA.getPeriod(); because the sizes of shortEMA and longEMA are different. for (int i = longEMA.getPeriod(); i < longEMA.getPeriod() + period; i++) { //i value with shortEMA gets changed to compensate the list size difference - currentMACD = shortEMA.getEMAhistory().get(i + periodDifference) - longEMA.getEMAhistory().get(i); + currentMACD = shortEMA.getHistory().get(i + periodDifference) - longEMA.getHistory().get(i); currentSignal += currentMACD; } currentSignal = currentSignal / (double) period; //Everything after the first calculation of signal line. - for (int i = longEMA.getPeriod() + period; i < longEMA.getEMAhistory().size(); i++) { - currentMACD = shortEMA.getEMAhistory().get(i + periodDifference) - longEMA.getEMAhistory().get(i); + for (int i = longEMA.getPeriod() + period; i < longEMA.getHistory().size(); i++) { + currentMACD = shortEMA.getHistory().get(i + periodDifference) - longEMA.getHistory().get(i); currentSignal = currentMACD * multiplier + currentSignal * (1 - multiplier); } @@ -79,13 +81,13 @@ public void update(double newPrice) { @Override public int check(double newPrice) { double change = (getTemp(newPrice) - lastTick) / Math.abs(lastTick); - if (change > MACD.SIGNAL_CHANGE && get() < 0) { + if (change > config.getRequiredChange() && get() < 0) { explanation = "MACD histogram grew by " + Formatter.formatPercent(change); - return 1; + return config.getWeight(); } - /*if (change < -MACD.change) { + /*if (change < -config.getRequiredChange()) { explanation = "MACD histogram fell by " + Formatter.formatPercent(change); - return -1; + return -config.GetWeight(); }*/ explanation = ""; return 0; diff --git a/src/main/java/indicators/RSI.java b/src/main/java/indicators/RSI.java index d3cd0df..fa40764 100644 --- a/src/main/java/indicators/RSI.java +++ b/src/main/java/indicators/RSI.java @@ -1,34 +1,34 @@ package indicators; +import data.config.RsiConfig; import system.Formatter; import java.util.List; +//Common period for RSI is 14 public class RSI implements Indicator { + private final RsiConfig config; + private final int period; private double avgUp; private double avgDwn; private double prevClose; - private final int period; private String explanation; - public static int POSITIVE_MIN; - public static int POSITIVE_MAX; - public static int NEGATIVE_MIN; - public static int NEGATIVE_MAX; - public RSI(List closingPrice, int period) { + public RSI(List warmupData, RsiConfig config) { avgUp = 0; avgDwn = 0; - this.period = period; explanation = ""; - init(closingPrice); + this.config = config; + this.period = config.getPeriod(); + init(warmupData); } @Override - public void init(List closingPrices) { - prevClose = closingPrices.get(0); + public void init(List warmupData) { + prevClose = warmupData.get(0); for (int i = 1; i < period + 1; i++) { - double change = closingPrices.get(i) - prevClose; + double change = warmupData.get(i) - prevClose; if (change > 0) { avgUp += change; } else { @@ -41,8 +41,8 @@ public void init(List closingPrices) { avgDwn = avgDwn / (double) period; //Dont use latest unclosed value - for (int i = period + 1; i < closingPrices.size() - 1; i++) { - update(closingPrices.get(i)); + for (int i = period + 1; i < warmupData.size() - 1; i++) { + update(warmupData.get(i)); } } @@ -82,21 +82,21 @@ public void update(double newPrice) { @Override public int check(double newPrice) { double temp = getTemp(newPrice); - if (temp < POSITIVE_MIN) { + if (temp < config.getPositiveMin()) { explanation = "RSI of " + Formatter.formatDecimal(temp); - return 2; + return 2 * config.getWeight(); } - if (temp < POSITIVE_MAX) { + if (temp < config.getPositiveMax()) { explanation = "RSI of " + Formatter.formatDecimal(temp); - return 1; + return config.getWeight(); } - if (temp > NEGATIVE_MIN) { + if (temp > config.getNegativeMin()) { explanation = "RSI of " + Formatter.formatDecimal(temp); - return -1; + return -config.getWeight(); } - if (temp > NEGATIVE_MAX) { + if (temp > config.getNegativeMax()) { explanation = "RSI of " + Formatter.formatDecimal(temp); - return -2; + return -2 * config.getWeight(); } explanation = ""; return 0; diff --git a/src/main/java/indicators/SMA.java b/src/main/java/indicators/SMA.java index 94bdc4b..8e886e1 100644 --- a/src/main/java/indicators/SMA.java +++ b/src/main/java/indicators/SMA.java @@ -4,15 +4,15 @@ import java.util.List; public class SMA implements Indicator { - - private double currentSum; private final int period; private final LinkedList prices; - public SMA(List closingPrices, int period) { + private double currentSum; + + public SMA(List warmupData, int period) { this.period = period; prices = new LinkedList<>(); - init(closingPrices); + init(warmupData); } @Override @@ -26,13 +26,13 @@ public double getTemp(double newPrice) { } @Override - public void init(List closingPrices) { - if (period > closingPrices.size()) return; + public void init(List warmupData) { + if (period > warmupData.size()) return; //Initial sum - for (int i = closingPrices.size() - period - 1; i < closingPrices.size() - 1; i++) { - prices.add(closingPrices.get(i)); - currentSum += (closingPrices.get(i)); + for (int i = warmupData.size() - period - 1; i < warmupData.size() - 1; i++) { + prices.add(warmupData.get(i)); + currentSum += (warmupData.get(i)); } } diff --git a/src/main/java/system/Collection.java b/src/main/java/system/Collection.java index 0146534..559ee61 100644 --- a/src/main/java/system/Collection.java +++ b/src/main/java/system/Collection.java @@ -6,6 +6,7 @@ import com.binance.api.client.domain.market.Candlestick; import com.binance.api.client.domain.market.CandlestickInterval; import com.binance.api.client.exception.BinanceApiException; +import data.config.Config; import data.price.PriceBean; import data.price.PriceReader; import data.price.PriceWriter; diff --git a/src/main/java/system/InstanceEndpoint.java b/src/main/java/system/InstanceEndpoint.java new file mode 100644 index 0000000..c28c4b0 --- /dev/null +++ b/src/main/java/system/InstanceEndpoint.java @@ -0,0 +1,27 @@ +package system; + +import java.io.*; +import java.net.ServerSocket; +import java.net.Socket; + +public class InstanceEndpoint { + public static void main(String[] args) throws IOException { + ServerSocket ss = new ServerSocket(8080); + System.out.println("Listening on port 8080"); + while (true) { + Socket socket = ss.accept(); + if (socket.isConnected()) { + try (DataInputStream in = new DataInputStream(socket.getInputStream()); + DataOutputStream out = new DataOutputStream(socket.getOutputStream())) { + out.writeUTF("{\"type\":\"trade\",\"trade\":{\"profit\":0.1,\"profitable\":true,\"name\":\"big cocka\"}}"); + while (true) { + System.out.println(in.readUTF()); + } + } catch (IOException e) { + System.out.println("connection aborted with client " + socket.toString()); + ; + } + } + } + } +} diff --git a/src/main/java/trading/Currency.java b/src/main/java/trading/Currency.java index 5946841..4949e7f 100644 --- a/src/main/java/trading/Currency.java +++ b/src/main/java/trading/Currency.java @@ -3,13 +3,12 @@ import com.binance.api.client.BinanceApiWebSocketClient; import com.binance.api.client.domain.market.Candlestick; import com.binance.api.client.domain.market.CandlestickInterval; +import data.config.IndicatorConfig; import data.price.PriceBean; import data.price.PriceReader; -import indicators.DBB; import indicators.Indicator; -import indicators.MACD; -import indicators.RSI; import system.BinanceAPI; +import data.config.Config; import system.Formatter; import java.io.Closeable; @@ -26,8 +25,6 @@ import java.util.stream.Collectors; public class Currency implements Closeable { - public static int CONFLUENCE_TARGET; - private final String pair; private LocalAccount account; private Trade activeTrade; @@ -51,10 +48,10 @@ public Currency(String coin, LocalAccount account) { //Every currency needs to contain and update our indicators List history = BinanceAPI.get().getCandlestickBars(pair, CandlestickInterval.FIVE_MINUTES); - List closingPrices = history.stream().map(candle -> Double.parseDouble(candle.getClose())).collect(Collectors.toList()); - indicators.add(new RSI(closingPrices, 14)); - indicators.add(new MACD(closingPrices, 12, 26, 9)); - indicators.add(new DBB(closingPrices, 20)); + List indicatorWarmupPrices = history.stream().map(candle -> Double.parseDouble(candle.getClose())).collect(Collectors.toList()); + for (IndicatorConfig indicatorConfig : Config.get(this).getIndicators()) { + indicators.add(indicatorConfig.toIndicator(indicatorWarmupPrices)); + } //We set the initial values to check against in onMessage based on the latest candle in history currentTime = System.currentTimeMillis(); @@ -91,15 +88,14 @@ public Currency(String pair, String filePath, LocalAccount account) { PriceBean bean = reader.readPrice(); firstBean = bean; - List closingPrices = new ArrayList<>(); + List indicatorWarmupPrices = new ArrayList<>(); while (bean.isClosing()) { - closingPrices.add(bean.getPrice()); + indicatorWarmupPrices.add(bean.getPrice()); bean = reader.readPrice(); } - //TODO: Fix slight mismatch between MACD backtesting and server values. - indicators.add(new RSI(closingPrices, 14)); - indicators.add(new MACD(closingPrices, 12, 26, 9)); - indicators.add(new DBB(closingPrices, 20)); + for (IndicatorConfig indicatorConfig : Config.get(this).getIndicators()) { + indicators.add(indicatorConfig.toIndicator(indicatorWarmupPrices)); + } while (bean != null) { accept(bean); bean = reader.readPrice(); @@ -130,12 +126,12 @@ private void accept(PriceBean bean) { int confluence = 0; //0 Confluence should be reserved in the config for doing nothing currentlyCalculating.set(true); //We can disable the strategy and trading logic to only check indicator and price accuracy - if ((Trade.CLOSE_USE_CONFLUENCE && hasActiveTrade()) || account.enoughFunds()) { + if ((Config.get(this).useConfluenceToClose() && hasActiveTrade()) || account.enoughFunds()) { confluence = check(); } if (hasActiveTrade()) { //We only allow one active trade per currency, this means we only need to do one of the following: activeTrade.update(currentPrice, confluence);//Update the active trade stop-loss and high values - } else if (confluence >= CONFLUENCE_TARGET && account.enoughFunds()) { + } else if (confluence >= Config.get(this).getConfluenceToOpen() && account.enoughFunds()) { account.open(Currency.this, "Trade opened due to: " + getExplanations()); } currentlyCalculating.set(false); @@ -193,7 +189,7 @@ public void log(String path) { try (FileWriter writer = new FileWriter(path)) { writer.write("Test ended " + system.Formatter.formatDate(LocalDateTime.now()) + " \n"); writer.write("\n\nCONFIG:\n"); - writer.write(account.getInstance().getConfig().toString()); + writer.write(Config.get(this).toString()); writer.write("\n\nMarket performance: " + system.Formatter.formatPercent((currentPrice - firstBean.getPrice()) / firstBean.getPrice())); if (!tradeHistory.isEmpty()) { tradeHistory.sort(Comparator.comparingDouble(Trade::getProfit)); diff --git a/src/main/java/trading/Instance.java b/src/main/java/trading/Instance.java index c1f842a..7581764 100644 --- a/src/main/java/trading/Instance.java +++ b/src/main/java/trading/Instance.java @@ -6,7 +6,7 @@ import com.binance.api.client.exception.BinanceApiException; import data.config.ConfigData; import system.BinanceAPI; -import system.Config; +import data.config.Config; import system.Formatter; import java.io.Closeable; diff --git a/src/main/java/trading/LocalAccount.java b/src/main/java/trading/LocalAccount.java index 9b01613..9a2a13f 100644 --- a/src/main/java/trading/LocalAccount.java +++ b/src/main/java/trading/LocalAccount.java @@ -38,10 +38,6 @@ public class LocalAccount { private double takerCommission; private double buyerCommission; - /** - * Wallet value will most probably be 0 at first, but you could start - * with an existing wallet value as well. - */ public LocalAccount(Instance instance, double startingValue) { this.instance = instance; this.startingValue = startingValue; diff --git a/src/main/java/trading/Trade.java b/src/main/java/trading/Trade.java index 1c1c543..a1ec464 100644 --- a/src/main/java/trading/Trade.java +++ b/src/main/java/trading/Trade.java @@ -1,16 +1,12 @@ package trading; +import data.config.Config; import system.Formatter; public class Trade { private double high; //Set the highest price - public static double TRAILING_SL; //It's in percentages, but using double for comfort. - public static double TAKE_PROFIT; //It's in percentages, but using double for comfort. - public static boolean CLOSE_USE_CONFLUENCE; - public static int CLOSE_CONFLUENCE; - private final long openTime; private final double entryPrice; //Starting price of a trade (when logic decides to buy) private final Currency currency; //What cryptocurrency is used. @@ -30,8 +26,6 @@ public Trade(Currency currency, double entryPrice, double amount, String explana } //Getters and setters - - public String getExplanation() { return explanation; } @@ -97,19 +91,22 @@ public long getDuration() { public void update(double newPrice, int confluence) { if (newPrice > high) high = newPrice; - if (getProfit() > TAKE_PROFIT) { + //TP + if (getProfit() > Config.get(this).getTakeProfit()) { explanation += "Closed due to: Take profit"; currency.getAccount().close(this); return; } - if (newPrice < high * (1 - TRAILING_SL)) { + //Trailing SL + if (newPrice < high * (1 - Config.get(this).getTrailingSl())) { explanation += "Closed due to: Trailing SL"; currency.getAccount().close(this); return; } - if (CLOSE_USE_CONFLUENCE && confluence <= -CLOSE_CONFLUENCE) { + //Confluence to close + if (Config.get(this).useConfluenceToClose() && confluence <= -Config.get(this).getConfluenceToClose()) { explanation += "Closed due to: Indicator confluence of " + confluence; currency.getAccount().close(this); } From a8a448c01c4f7254194eb3163552a349c63814ac Mon Sep 17 00:00:00 2001 From: Markus Aksli Date: Sun, 16 May 2021 16:59:45 +0300 Subject: [PATCH 6/8] Added config toJson, SSL socket testing, SSL cert creation --- .gitignore | 3 + configs/full.yaml | 2 +- pom.xml | 1 + src/main/java/data/config/Config.java | 11 ++ .../java/data/config/IndicatorConfig.java | 2 - src/main/java/https/HTTPSServer.java | 137 ++++++++++++++++++ src/main/java/https/SSLCerter.java | 83 +++++++++++ src/main/java/system/InstanceEndpoint.java | 12 +- 8 files changed, 247 insertions(+), 4 deletions(-) create mode 100644 src/main/java/https/HTTPSServer.java create mode 100644 src/main/java/https/SSLCerter.java diff --git a/.gitignore b/.gitignore index cb004ae..3b8d857 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,6 @@ fabric.properties # Executable config /exe/ +/ks.jks +/TradeBot.p12 +/ts.jks diff --git a/configs/full.yaml b/configs/full.yaml index 35cad10..c4ebb3e 100644 --- a/configs/full.yaml +++ b/configs/full.yaml @@ -4,7 +4,7 @@ trailingSl: 0.1 takeProfit: 0.15 confluenceToOpen: 2 -confluenceToClose: 2 +# confluenceToClose: 2 indicators: - ! weight: 1 diff --git a/pom.xml b/pom.xml index 9917a1b..e6fdfd2 100644 --- a/pom.xml +++ b/pom.xml @@ -39,5 +39,6 @@ commons-io 2.8.0 + \ No newline at end of file diff --git a/src/main/java/data/config/Config.java b/src/main/java/data/config/Config.java index bb05f14..06f754e 100644 --- a/src/main/java/data/config/Config.java +++ b/src/main/java/data/config/Config.java @@ -2,6 +2,7 @@ import com.binance.api.client.domain.general.RateLimit; import com.binance.api.client.domain.general.RateLimitType; +import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; @@ -12,6 +13,7 @@ import java.io.File; import java.io.IOException; +import java.util.Arrays; public class Config { private static final int REQUEST_LIMIT = BinanceAPI.get().getExchangeInfo().getRateLimits().stream() @@ -59,6 +61,15 @@ public void update() throws ConfigException { data.update(readValues()); } + public String toJson() { + ObjectMapper objectMapper = new ObjectMapper(new JsonFactory()); + try { + return objectMapper.writeValueAsString(data); + } catch (JsonProcessingException e) { + return "Failed to serialize config: " + e.getMessage(); + } + } + @Override public String toString() { ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()); diff --git a/src/main/java/data/config/IndicatorConfig.java b/src/main/java/data/config/IndicatorConfig.java index 539ac71..c82b6f2 100644 --- a/src/main/java/data/config/IndicatorConfig.java +++ b/src/main/java/data/config/IndicatorConfig.java @@ -30,8 +30,6 @@ public int getWeight() { } public void setWeight(int weight) { - - System.out.println(getClass()); this.weight = weight; } } diff --git a/src/main/java/https/HTTPSServer.java b/src/main/java/https/HTTPSServer.java new file mode 100644 index 0000000..9bc85d6 --- /dev/null +++ b/src/main/java/https/HTTPSServer.java @@ -0,0 +1,137 @@ +package https; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.security.KeyStore; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; + +public class HTTPSServer { + private int port = 9999; + private boolean isServerDone = false; + + public static void main(String[] args) { + HTTPSServer server = new HTTPSServer(); + server.run(); + } + + HTTPSServer() { + } + + HTTPSServer(int port) { + this.port = port; + } + + // Create the and initialize the SSLContext + private SSLContext createSSLContext() { + try { + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(new FileInputStream("ks.jks"), "123456".toCharArray()); + + // Create key manager + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); + keyManagerFactory.init(keyStore, "654321".toCharArray()); + KeyManager[] km = keyManagerFactory.getKeyManagers(); + + // Create trust manager + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509"); + trustManagerFactory.init(keyStore); + TrustManager[] tm = trustManagerFactory.getTrustManagers(); + + // Initialize SSLContext + SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); + sslContext.init(km, tm, null); + + return sslContext; + } catch (Exception ex) { + ex.printStackTrace(); + } + + return null; + } + + // Start to run the server + public void run() { + SSLContext sslContext = this.createSSLContext(); + + try { + // Create server socket factory + SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory(); + + // Create server socket + SSLServerSocket sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(this.port); + + System.out.println("SSL server started"); + while (!isServerDone) { + SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept(); + + // Start the server thread + new ServerThread(sslSocket).start(); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + // Thread handling the socket from client + static class ServerThread extends Thread { + private SSLSocket sslSocket = null; + + ServerThread(SSLSocket sslSocket) { + this.sslSocket = sslSocket; + } + + public void run() { + sslSocket.setEnabledCipherSuites(sslSocket.getSupportedCipherSuites()); + + try { + // Start handshake + sslSocket.startHandshake(); + + // Get session after the connection is established + SSLSession sslSession = sslSocket.getSession(); + + System.out.println("SSLSession :"); + System.out.println("\tProtocol : " + sslSession.getProtocol()); + System.out.println("\tCipher suite : " + sslSession.getCipherSuite()); + + // Start handling application content + InputStream inputStream = sslSocket.getInputStream(); + OutputStream outputStream = sslSocket.getOutputStream(); + + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(outputStream)); + + // Write data + printWriter.print("Java hello"); + printWriter.flush(); + + String line; + while ((line = bufferedReader.readLine()) != null) { + System.out.println("Inut : " + line); + + if (line.trim().isEmpty()) { + break; + } + } + System.out.println("Closed connection to " + sslSession.getPeerHost()); + sslSocket.close(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/https/SSLCerter.java b/src/main/java/https/SSLCerter.java new file mode 100644 index 0000000..5de9d67 --- /dev/null +++ b/src/main/java/https/SSLCerter.java @@ -0,0 +1,83 @@ +package https; + +import sun.security.x509.*; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.net.InetAddress; +import java.security.*; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Date; + +public class SSLCerter { + public static void main(String[] args) { + String password = "123456"; + String keyPassword = "654321"; + try ( + FileOutputStream keyFos = new FileOutputStream("ks.jks"); FileOutputStream trustFos = new FileOutputStream("ts.jks"); + ) { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(4096); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + + X509Certificate[] chain = {generateCertificate("CN=" + InetAddress.getLocalHost().getHostName() + "localhost, OU=Tradebot, O=lower third, C=EE", keyPair, 365 * 10, "SHA256withRSA")}; + + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(null, null); + keyStore.setKeyEntry("main", keyPair.getPrivate(), keyPassword.toCharArray(), chain); + keyStore.store(keyFos, password.toCharArray()); + + keyStore = KeyStore.getInstance("JKS"); + keyStore.load(null, null); + keyStore.setCertificateEntry("main", chain[0]); + keyStore.store(trustFos, password.toCharArray()); + + //sun.security.tools.keytool.Main.main(new String[]{"-export", "-keystore", "ts.jks", "-alias", "main", "-file", "TradeBot.cer", "-storepass", password}); + sun.security.tools.keytool.Main.main(new String[]{"-importkeystore", "-srckeystore", "ts.jks", "-srcalias", "main", "-srcstorepass", password, "-destkeystore", "TradeBot.p12", "-deststorepass", password}); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + /** + * Create a self-signed X.509 Certificate + * + * @param dn the X.509 Distinguished Name, eg "CN=Test, L=London, C=GB" + * @param pair the KeyPair + * @param days how many days from now the Certificate is valid for + * @param algorithm the signing algorithm, eg "SHA1withRSA" + */ + static X509Certificate generateCertificate(String dn, KeyPair pair, int days, String algorithm) + throws GeneralSecurityException, IOException { + PrivateKey privkey = pair.getPrivate(); + X509CertInfo info = new X509CertInfo(); + Date from = new Date(); + Date to = new Date(from.getTime() + days * 86400000l); + CertificateValidity interval = new CertificateValidity(from, to); + BigInteger sn = new BigInteger(64, new SecureRandom()); + X500Name owner = new X500Name(dn); + + info.set(X509CertInfo.VALIDITY, interval); + info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn)); + info.set(X509CertInfo.SUBJECT, owner); + info.set(X509CertInfo.ISSUER, owner); + info.set(X509CertInfo.KEY, new CertificateX509Key(pair.getPublic())); + info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3)); + AlgorithmId algo = new AlgorithmId(AlgorithmId.md5WithRSAEncryption_oid); + info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algo)); + + // Sign the cert to identify the algorithm that's used. + X509CertImpl cert = new X509CertImpl(info); + cert.sign(privkey, algorithm); + + // Update the algorith, and resign. + algo = (AlgorithmId) cert.get(X509CertImpl.SIG_ALG); + info.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, algo); + cert = new X509CertImpl(info); + cert.sign(privkey, algorithm); + return cert; + } +} diff --git a/src/main/java/system/InstanceEndpoint.java b/src/main/java/system/InstanceEndpoint.java index c28c4b0..8342b2a 100644 --- a/src/main/java/system/InstanceEndpoint.java +++ b/src/main/java/system/InstanceEndpoint.java @@ -1,11 +1,21 @@ package system; +import data.config.Config; +import data.config.ConfigException; + import java.io.*; import java.net.ServerSocket; import java.net.Socket; public class InstanceEndpoint { public static void main(String[] args) throws IOException { + Config config = null; + try { + config = new Config("configs/full.yaml"); + } catch (ConfigException e) { + e.printStackTrace(); + } + ServerSocket ss = new ServerSocket(8080); System.out.println("Listening on port 8080"); while (true) { @@ -13,7 +23,7 @@ public static void main(String[] args) throws IOException { if (socket.isConnected()) { try (DataInputStream in = new DataInputStream(socket.getInputStream()); DataOutputStream out = new DataOutputStream(socket.getOutputStream())) { - out.writeUTF("{\"type\":\"trade\",\"trade\":{\"profit\":0.1,\"profitable\":true,\"name\":\"big cocka\"}}"); + out.writeUTF(config.toJson()); while (true) { System.out.println(in.readUTF()); } From 1b13ba4782ff0f929e78ab9b1700ca5faa2c8cff Mon Sep 17 00:00:00 2001 From: Markus Aksli Date: Thu, 8 Jul 2021 17:26:51 +0300 Subject: [PATCH 7/8] Added proper instance constructors --- .idea/misc.xml | 6 +- pom.xml | 6 +- src/main/java/data/config/Config.java | 4 + src/main/java/data/price/PriceReader.java | 45 +++++- src/main/java/system/BinanceAPI.java | 17 ++- src/main/java/trading/Currency.java | 11 +- src/main/java/trading/Instance.java | 159 ++++++++-------------- src/main/java/trading/LocalAccount.java | 65 ++------- 8 files changed, 140 insertions(+), 173 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index 92a9595..e6549f1 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,9 @@ + + + + - + \ No newline at end of file diff --git a/pom.xml b/pom.xml index e6fdfd2..bbcfbff 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,10 @@ commons-io 2.8.0 - + + com.ea.async + ea-async + 1.2.3 + \ No newline at end of file diff --git a/src/main/java/data/config/Config.java b/src/main/java/data/config/Config.java index 06f754e..972c464 100644 --- a/src/main/java/data/config/Config.java +++ b/src/main/java/data/config/Config.java @@ -28,6 +28,10 @@ public Config(String path) throws ConfigException { data = readValues(); } + public String name() { + return configFile.getName(); + } + public static ConfigData get(Trade trade) { return trade.getCurrency().getAccount().getInstance().getConfig(); } diff --git a/src/main/java/data/price/PriceReader.java b/src/main/java/data/price/PriceReader.java index 9f4b668..d99f849 100644 --- a/src/main/java/data/price/PriceReader.java +++ b/src/main/java/data/price/PriceReader.java @@ -1,21 +1,52 @@ package data.price; import java.io.*; +import java.nio.file.Path; public class PriceReader implements Closeable { private final DataInputStream stream; + private Path path; + private String coin; + private String fiat; + private long startTime; + private long endTime; - public PriceReader(String file) throws FileNotFoundException { + public String getCoin() { + return coin; + } + + public String getFiat() { + return fiat; + } + + public long getStartTime() { + return startTime; + } + + public long getEndTime() { + return endTime; + } + + public String getPair() { + return coin + fiat; + } + + public Path getPath() { + return path; + } + + public PriceReader(String file) throws IOException { this.stream = new DataInputStream(new BufferedInputStream(new FileInputStream(file))); + this.path = Path.of(file); + this.coin = stream.readUTF(); + this.fiat = stream.readUTF(); + this.startTime = stream.readLong(); + this.endTime = stream.readLong(); } - public PriceBean readPrice() { - try { - return new PriceBean(stream.readLong(), stream.readDouble(), stream.readBoolean()); - } catch (IOException e) { - return null; - } + public PriceBean readPrice() throws IOException { + return new PriceBean(stream.readLong(), stream.readDouble(), stream.readBoolean()); } @Override diff --git a/src/main/java/system/BinanceAPI.java b/src/main/java/system/BinanceAPI.java index 2296ec2..859668e 100644 --- a/src/main/java/system/BinanceAPI.java +++ b/src/main/java/system/BinanceAPI.java @@ -2,17 +2,23 @@ import com.binance.api.client.BinanceApiClientFactory; import com.binance.api.client.BinanceApiRestClient; +import com.binance.api.client.domain.account.Account; public final class BinanceAPI { - private static final BinanceApiClientFactory factory = BinanceApiClientFactory.newInstance(); + private static BinanceApiClientFactory factory = BinanceApiClientFactory.newInstance(); private static BinanceApiRestClient defaultClient; + private static Account account; + private static boolean loggedIn; private BinanceAPI() { throw new IllegalStateException("Utility class"); } - public static BinanceApiClientFactory login(String apiKey, String secretKey) { - return BinanceApiClientFactory.newInstance(apiKey, secretKey); + public static void login(String apiKey, String secretKey) { + factory = BinanceApiClientFactory.newInstance(apiKey, secretKey); + defaultClient = factory.newRestClient(); + account = defaultClient.getAccount(); + loggedIn = true; } public static BinanceApiClientFactory getFactory() { @@ -25,4 +31,9 @@ public static BinanceApiRestClient get() { } return defaultClient; } + + public static Account getAccount() { + if (!loggedIn) return null; + return account; + } } diff --git a/src/main/java/trading/Currency.java b/src/main/java/trading/Currency.java index 4949e7f..d098268 100644 --- a/src/main/java/trading/Currency.java +++ b/src/main/java/trading/Currency.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; @@ -80,11 +81,13 @@ public Currency(String coin, LocalAccount account) { } //Used for BACKTESTING - public Currency(String pair, String filePath, LocalAccount account) { - this.pair = pair; + public Currency(PriceReader reader, LocalAccount account) { + this.pair = reader.getPair(); this.account = account; + } - try (PriceReader reader = new PriceReader(filePath)) { + public CompletableFuture runBacktest(PriceReader reader) { + try (reader) { PriceBean bean = reader.readPrice(); firstBean = bean; @@ -100,10 +103,10 @@ public Currency(String pair, String filePath, LocalAccount account) { accept(bean); bean = reader.readPrice(); } - } catch (IOException e) { e.printStackTrace(); } + return CompletableFuture.completedFuture(null); } private void accept(PriceBean bean) { diff --git a/src/main/java/trading/Instance.java b/src/main/java/trading/Instance.java index 7581764..5b4a726 100644 --- a/src/main/java/trading/Instance.java +++ b/src/main/java/trading/Instance.java @@ -5,6 +5,7 @@ import com.binance.api.client.domain.general.SymbolFilter; import com.binance.api.client.exception.BinanceApiException; import data.config.ConfigData; +import data.price.PriceReader; import system.BinanceAPI; import data.config.Config; import system.Formatter; @@ -13,16 +14,15 @@ import java.io.File; import java.io.IOException; import java.util.*; +import java.util.function.Function; public class Instance implements Closeable { - public static double STARTING_VALUE; - private static final File credentialsFile = new File("credentials.txt"); - - private final String ID; - private final LocalAccount account; - private final Mode mode; + private List currencies; private String fiat; private Config config; + private final LocalAccount account; + private final Mode mode; + private final String ID; public String getID() { return ID; @@ -36,7 +36,6 @@ public Mode getMode() { return mode; } - List currencies = new ArrayList<>(); @Override public void close() { @@ -49,11 +48,21 @@ public void close() { } } - public Instance(List coins, String ID) { - this.ID = ID; + /** + * Creates a simulation instance + * + * @param coins List of tradeable currencies against the fiat + * @param fiat FIAT currency + * @param config Loaded Config + * @param startingFIAT Predefined starting FIAT value + */ + public Instance(List coins, String fiat, Config config, double startingFIAT) { this.mode = Mode.SIMULATION; - this.account = new LocalAccount(this, STARTING_VALUE); - + this.fiat = fiat; + this.config = config; + this.ID = "Simulation" + "_" + config.name() + "_" + System.currentTimeMillis(); + this.account = new LocalAccount(this, startingFIAT); + List currencies = new ArrayList<>(); for (String arg : coins) { //The currency class contains all of the method calls that drive the activity of our bot try { @@ -65,56 +74,49 @@ public Instance(List coins, String ID) { } } - //LIVE - public Instance(List coins, String ID, String apiKey, String secretKey) { - this.ID = ID; - this.mode = Mode.LIVE; + //BACKTESTING + public Instance(PriceReader reader, Config config, double startingFIAT) { + this.mode = Mode.BACKTESTING; + this.fiat = reader.getFiat(); + this.config = config; + this.ID = "Backtesting" + "_" + config.name() + "_" + System.currentTimeMillis(); + this.account = new LocalAccount(this, startingFIAT); - /*boolean fileFailed = true; - if (credentialsFile.exists()) { - try { - final List strings = Files.readAllLines(credentialsFile.toPath()); - if (!strings.get(0).matches("\\*+")) { - account = new LocalAccount(strings.get(0), strings.get(1)); - fileFailed = false; - } else { - System.out.println("---credentials.txt has not been set up"); - } - } catch (Exception e) { - e.printStackTrace(); - System.out.println("---Failed to use credentials in credentials.txt"); + System.out.println("\n---Backtesting..."); + Currency currency = new Currency(reader, account); + currencies.add(currency); + currency.runBacktest(reader).thenApply(unused -> { + for (Trade trade : account.getActiveTrades()) { + trade.setExplanation(trade.getExplanation() + "Manually closed"); + account.close(trade); } - } else { - System.out.println("---credentials.txt file not detected!"); - } - - if (fileFailed) { - Scanner sc = new Scanner(System.in); - String apiKey; - String apiSecret; - while (true) { - System.out.println("Enter your API Key: "); - apiKey = sc.nextLine(); - if (apiKey.length() == 64) { - System.out.println("Enter your Secret Key: "); - apiSecret = sc.nextLine(); - if (apiSecret.length() == 64) { - break; - } else System.out.println("Secret API is incorrect, enter again."); - } else System.out.println("Incorrect API, enter again."); + int i = 1; + String path = "log/" + reader.getPath().getFileName(); + String resultPath = path.replace(".dat", "_run_" + i + ".txt"); + while (new File(resultPath).exists()) { + i++; + resultPath = path.replace(".dat", "_run_" + i + ".txt"); } - }*/ - account = new LocalAccount(this, apiKey, secretKey); - System.out.println("Can trade: " + account.getRealAccount().isCanTrade()); - System.out.println(account.getMakerCommission() + " Maker commission."); - System.out.println(account.getBuyerCommission() + " Buyer commission"); - System.out.println(account.getTakerCommission() + " Taker comission"); + new File("log").mkdir(); + currency.log(resultPath); + + return null; + }); + } + + //LIVE + public Instance(List coins, String fiat, Config config) { + this.mode = Mode.LIVE; + this.fiat = fiat; + this.config = config; + this.ID = "Backtesting" + "_" + config.name() + "_" + System.currentTimeMillis(); + this.account = new LocalAccount(this, 0); //TODO: Open price for existing currencies String current = ""; try { List addedCurrencies = new ArrayList<>(); - for (AssetBalance balance : account.getRealAccount().getBalances()) { + for (AssetBalance balance : BinanceAPI.getAccount().getBalances()) { if (balance.getFree().matches("0\\.0+")) continue; if (coins.contains(balance.getAsset())) { current = balance.getAsset(); @@ -159,58 +161,9 @@ public Instance(List coins, String ID, String apiKey, String secretKey) } } - //BACKTESTING - public Instance(String path) { - this.ID = path; - this.account = new LocalAccount(this, STARTING_VALUE); - this.mode = Mode.BACKTESTING; - - /* - final String[] backtestingFiles = Collection.getDataFiles(); - if (backtestingFiles.length == 0) { - System.out.println("No backtesting files detected!"); - System.exit(0); - } - Scanner sc = new Scanner(System.in); - System.out.println("\nBacktesting data files:\n"); - for (int i = 0; i < backtestingFiles.length; i++) { - System.out.println("[" + (i + 1) + "] " + backtestingFiles[i]); - } - System.out.println("\nEnter a number to select the backtesting data file"); - String input = sc.nextLine(); - if (!input.matches("\\d+")) continue; - int index = Integer.parseInt(input); - if (index > backtestingFiles.length) { - - } - String path = "backtesting/" + backtestingFiles[index - 1]; - */ - - System.out.println("\n---Backtesting..."); - Currency currency = new Currency(new File(path).getName().split("_")[0], path, account); - currencies.add(currency); - - for (Trade trade : account.getActiveTrades()) { - trade.setExplanation(trade.getExplanation() + "Manually closed"); - account.close(trade); - } - - - int i = 1; - path = path.replace("backtesting", "log"); - String resultPath = path.replace(".dat", "_run_" + i + ".txt"); - while (new File(resultPath).exists()) { - i++; - resultPath = path.replace(".dat", "_run_" + i + ".txt"); - } - new File("log").mkdir(); - - currency.log(resultPath); - } - public void refreshWalletAndTrades() { if (mode != Mode.LIVE) return; - for (AssetBalance balance : account.getRealAccount().getBalances()) { + for (AssetBalance balance : BinanceAPI.getAccount().getBalances()) { if (balance.getFree().matches("0\\.0+")) continue; if (balance.getAsset().equals(fiat)) { final double amount = Double.parseDouble(balance.getFree()); diff --git a/src/main/java/trading/LocalAccount.java b/src/main/java/trading/LocalAccount.java index 9a2a13f..d221dc7 100644 --- a/src/main/java/trading/LocalAccount.java +++ b/src/main/java/trading/LocalAccount.java @@ -1,8 +1,6 @@ package trading; -import com.binance.api.client.BinanceApiRestClient; import com.binance.api.client.domain.OrderStatus; -import com.binance.api.client.domain.account.Account; import com.binance.api.client.domain.account.NewOrderResponse; import com.binance.api.client.domain.account.NewOrderResponseType; import com.binance.api.client.domain.general.FilterType; @@ -25,8 +23,6 @@ public class LocalAccount { private final Instance instance; - private Account realAccount; - private BinanceApiRestClient client; //To give the account a specific final amount of money. private double fiatValue; @@ -34,48 +30,21 @@ public class LocalAccount { private final ConcurrentHashMap wallet; private final List tradeHistory; private final List activeTrades; - private double makerCommission; - private double takerCommission; - private double buyerCommission; public LocalAccount(Instance instance, double startingValue) { this.instance = instance; - this.startingValue = startingValue; - fiatValue = startingValue; - wallet = new ConcurrentHashMap<>(); - tradeHistory = new ArrayList<>(); - activeTrades = new CopyOnWriteArrayList<>(); - } - public LocalAccount(Instance instance, String apiKey, String secretApiKey) { - this.instance = instance; - client = BinanceAPI.login(apiKey, secretApiKey).newRestClient(); + this.startingValue = startingValue; + if (instance.getMode().equals(Instance.Mode.LIVE)) { + fiatValue = Double.parseDouble(BinanceAPI.getAccount().getAssetBalance(instance.getFiat()).getFree()); + } else { + fiatValue = startingValue; + } + System.out.println("---Starting FIAT: " + Formatter.formatDecimal(fiatValue) + " " + instance.getFiat()); wallet = new ConcurrentHashMap<>(); tradeHistory = new ArrayList<>(); activeTrades = new CopyOnWriteArrayList<>(); - realAccount = client.getAccount(); - if (!realAccount.isCanTrade()) { - System.out.println("Can't trade!"); - } - makerCommission = realAccount.getMakerCommission(); //Maker fees are - // paid when you add liquidity to our order book - // by placing a limit order below the ticker price for buy, and above the ticker price for sell. - takerCommission = realAccount.getTakerCommission();//Taker fees are paid when you remove - // liquidity from our order book by placing any order that is executed against an order on the order book. - buyerCommission = realAccount.getBuyerCommission(); - - //Example: If the current market/ticker price is $2000 for 1 BTC and you market buy bitcoins starting at the market price of $2000, then you will pay the taker fee. In this instance, you have taken liquidity/coins from the order book. - // - //If the current market/ticker price is $2000 for 1 BTC and you - //place a limit buy for bitcoins at $1995, then - //you will pay the maker fee IF the market/ticker price moves into your limit order at $1995. - fiatValue = Double.parseDouble(realAccount.getAssetBalance(instance.getFiat()).getFree()); - System.out.println("---Starting FIAT: " + Formatter.formatDecimal(fiatValue) + " " + instance.getFiat()); - } - - public Account getRealAccount() { - return realAccount; } //All backend.Trade methods @@ -170,18 +139,6 @@ public void removeFromWallet(Currency key, double value) { wallet.put(key, wallet.get(key) - value); } - public double getMakerCommission() { - return makerCommission; - } - - public double getTakerCommission() { - return takerCommission; - } - - public double getBuyerCommission() { - return buyerCommission; - } - public boolean enoughFunds() { return nextAmount() != 0; } @@ -298,10 +255,10 @@ private NewOrderResponse placeOrder(Currency currency, double amount, boolean bu System.out.println("\n---Placing a " + (buy ? "buy" : "sell") + " market order for " + currency.getPair()); BigDecimal originalDecimal = BigDecimal.valueOf(amount); //Round amount to base precision and LOT_SIZE - int precision = client.getExchangeInfo().getSymbolInfo(currency.getPair()).getBaseAssetPrecision(); + int precision = BinanceAPI.get().getExchangeInfo().getSymbolInfo(currency.getPair()).getBaseAssetPrecision(); String lotSize; - Optional minQtyOptional = client.getExchangeInfo().getSymbolInfo(currency.getPair()).getFilters().stream().filter(f -> FilterType.LOT_SIZE == f.getFilterType()).findFirst().map(f1 -> f1.getMinQty()); - Optional minNotational = client.getExchangeInfo().getSymbolInfo(currency.getPair()).getFilters().stream().filter(f -> FilterType.MIN_NOTIONAL == f.getFilterType()).findFirst().map(SymbolFilter::getMinNotional); + Optional minQtyOptional = BinanceAPI.get().getExchangeInfo().getSymbolInfo(currency.getPair()).getFilters().stream().filter(f -> FilterType.LOT_SIZE == f.getFilterType()).findFirst().map(f1 -> f1.getMinQty()); + Optional minNotational = BinanceAPI.get().getExchangeInfo().getSymbolInfo(currency.getPair()).getFilters().stream().filter(f -> FilterType.MIN_NOTIONAL == f.getFilterType()).findFirst().map(SymbolFilter::getMinNotional); if (minQtyOptional.isPresent()) { lotSize = minQtyOptional.get(); } else { @@ -330,7 +287,7 @@ private NewOrderResponse placeOrder(Currency currency, double amount, boolean bu NewOrderResponse order; try { - order = client.newOrder( + order = BinanceAPI.get().newOrder( buy ? marketBuy(currency.getPair(), convertedAmount).newOrderRespType(NewOrderResponseType.FULL) : marketSell(currency.getPair(), convertedAmount).newOrderRespType(NewOrderResponseType.FULL)); From af818768256bb265470727c9288a4c451b5ac690 Mon Sep 17 00:00:00 2001 From: Markus Aksli Date: Mon, 8 Nov 2021 12:14:51 +0200 Subject: [PATCH 8/8] RSI check fix --- src/main/java/indicators/RSI.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/indicators/RSI.java b/src/main/java/indicators/RSI.java index fa40764..d9cb43a 100644 --- a/src/main/java/indicators/RSI.java +++ b/src/main/java/indicators/RSI.java @@ -90,14 +90,14 @@ public int check(double newPrice) { explanation = "RSI of " + Formatter.formatDecimal(temp); return config.getWeight(); } - if (temp > config.getNegativeMin()) { - explanation = "RSI of " + Formatter.formatDecimal(temp); - return -config.getWeight(); - } if (temp > config.getNegativeMax()) { explanation = "RSI of " + Formatter.formatDecimal(temp); return -2 * config.getWeight(); } + if (temp > config.getNegativeMin()) { + explanation = "RSI of " + Formatter.formatDecimal(temp); + return -config.getWeight(); + } explanation = ""; return 0; }