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/.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/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/configs/example.yaml b/configs/example.yaml
new file mode 100644
index 0000000..a549712
--- /dev/null
+++ b/configs/example.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/configs/full.yaml b/configs/full.yaml
new file mode 100644
index 0000000..c4ebb3e
--- /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/pom.xml b/pom.xml
index 1d57f5d..bbcfbff 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
@@ -29,5 +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/PriceReader.java b/src/main/java/data/PriceReader.java
deleted file mode 100644
index eb6bcf5..0000000
--- a/src/main/java/data/PriceReader.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package data;
-
-import java.io.*;
-
-public class PriceReader implements Closeable {
-
- private final DataInputStream stream;
-
- public PriceReader(String file) throws FileNotFoundException {
- this.stream = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
- }
-
- public PriceBean readPrice() {
- try {
- return new PriceBean(stream.readLong(), stream.readDouble(), stream.readBoolean());
- } catch (IOException e) {
- return null;
- }
- }
-
- @Override
- public void close() throws IOException {
- stream.close();
- }
-}
diff --git a/src/main/java/data/config/Config.java b/src/main/java/data/config/Config.java
new file mode 100644
index 0000000..972c464
--- /dev/null
+++ b/src/main/java/data/config/Config.java
@@ -0,0 +1,86 @@
+package data.config;
+
+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;
+import system.BinanceAPI;
+import trading.Currency;
+import trading.LocalAccount;
+import trading.Trade;
+
+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()
+ .filter(rateLimit -> rateLimit.getRateLimitType().equals(RateLimitType.REQUEST_WEIGHT))
+ .findFirst().map(RateLimit::getLimit).orElse(1200);
+
+ private final File configFile;
+ private final ConfigData data;
+
+ public Config(String path) throws ConfigException {
+ configFile = new File(path);
+ data = readValues();
+ }
+
+ public String name() {
+ return configFile.getName();
+ }
+
+ 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() {
+ return REQUEST_LIMIT;
+ }
+
+ public ConfigData getData() {
+ return data;
+ }
+
+ public ConfigData readValues() throws ConfigException {
+ ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
+ try {
+ 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());
+ }
+
+ 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());
+ try {
+ return objectMapper.writeValueAsString(data);
+ } catch (JsonProcessingException e) {
+ return "Failed to serialize config: " + e.getMessage();
+ }
+ }
+}
diff --git a/src/main/java/data/config/ConfigData.java b/src/main/java/data/config/ConfigData.java
new file mode 100644
index 0000000..89d3ba0
--- /dev/null
+++ b/src/main/java/data/config/ConfigData.java
@@ -0,0 +1,102 @@
+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; //Number of indicators can't be changed
+
+ 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 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;
+ }
+
+ 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/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/IndicatorConfig.java b/src/main/java/data/config/IndicatorConfig.java
new file mode 100644
index 0000000..c82b6f2
--- /dev/null
+++ b/src/main/java/data/config/IndicatorConfig.java
@@ -0,0 +1,35 @@
+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) {
+ 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/RsiConfig.java b/src/main/java/data/config/RsiConfig.java
new file mode 100644
index 0000000..90bfa45
--- /dev/null
+++ b/src/main/java/data/config/RsiConfig.java
@@ -0,0 +1,97 @@
+package data.config;
+
+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 RsiConfig(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 RsiConfig() {
+ }
+
+ 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 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 +
+ ", 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/price/PriceReader.java b/src/main/java/data/price/PriceReader.java
new file mode 100644
index 0000000..d99f849
--- /dev/null
+++ b/src/main/java/data/price/PriceReader.java
@@ -0,0 +1,56 @@
+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 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() throws IOException {
+ return new PriceBean(stream.readLong(), stream.readDouble(), stream.readBoolean());
+ }
+
+ @Override
+ public void close() throws IOException {
+ stream.close();
+ }
+}
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/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/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..d9cb43a 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.getNegativeMax()) {
explanation = "RSI of " + Formatter.formatDecimal(temp);
- return -1;
+ return -2 * config.getWeight();
}
- if (temp > NEGATIVE_MAX) {
+ if (temp > config.getNegativeMin()) {
explanation = "RSI of " + Formatter.formatDecimal(temp);
- return -2;
+ return -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/modes/Backtesting.java b/src/main/java/modes/Backtesting.java
deleted file mode 100644
index 9cecd1c..0000000
--- a/src/main/java/modes/Backtesting.java
+++ /dev/null
@@ -1,76 +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);
- BuySell.setAccount(localAccount);
- 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);
- currencies.add(currency);
-
- for (Trade trade : localAccount.getActiveTrades()) {
- trade.setExplanation(trade.getExplanation() + "Manually closed");
- BuySell.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 217fb85..0000000
--- a/src/main/java/modes/Live.java
+++ /dev/null
@@ -1,175 +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");
- BuySell.setAccount(localAccount);
-
- //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);
- 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);
- 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));
- }
- }
- } 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 a10a669..0000000
--- a/src/main/java/modes/Simulation.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package modes;
-
-import com.binance.api.client.exception.BinanceApiException;
-import system.ConfigSetup;
-import trading.LocalAccount;
-import trading.BuySell;
-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);
- 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));
- } catch (BinanceApiException e) {
- System.out.println("---Could not add " + arg + ConfigSetup.getFiat());
- System.out.println(e.getMessage());
- }
- }
- }
-}
diff --git a/src/main/java/system/BinanceAPI.java b/src/main/java/system/BinanceAPI.java
new file mode 100644
index 0000000..859668e
--- /dev/null
+++ b/src/main/java/system/BinanceAPI.java
@@ -0,0 +1,39 @@
+package system;
+
+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 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 void login(String apiKey, String secretKey) {
+ factory = BinanceApiClientFactory.newInstance(apiKey, secretKey);
+ defaultClient = factory.newRestClient();
+ account = defaultClient.getAccount();
+ loggedIn = true;
+ }
+
+ public static BinanceApiClientFactory getFactory() {
+ return factory;
+ }
+
+ public static BinanceApiRestClient get() {
+ if (defaultClient == null) {
+ defaultClient = factory.newRestClient();
+ }
+ return defaultClient;
+ }
+
+ public static Account getAccount() {
+ if (!loggedIn) return null;
+ return account;
+ }
+}
diff --git a/src/main/java/modes/Collection.java b/src/main/java/system/Collection.java
similarity index 96%
rename from src/main/java/modes/Collection.java
rename to src/main/java/system/Collection.java
index bfd559c..559ee61 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;
@@ -6,13 +6,11 @@
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.config.Config;
+import data.price.PriceBean;
+import data.price.PriceReader;
+import data.price.PriceWriter;
import org.apache.commons.io.FileUtils;
-import system.ConfigSetup;
-import trading.CurrentAPI;
-import system.Formatter;
import java.io.*;
import java.nio.file.Files;
@@ -40,7 +38,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");
@@ -188,11 +186,11 @@ 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();
- CurrentAPI.get().getPrice(symbol);
+ symbol = sc.nextLine().toUpperCase();
+ BinanceAPI.get().getPrice(symbol);
break;
} catch (BinanceApiException e) {
System.out.println(e.getMessage());
@@ -246,9 +244,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() {
@@ -340,7 +338,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));
@@ -420,7 +418,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/ConfigSetup.java b/src/main/java/system/ConfigSetup.java
deleted file mode 100644
index 5fa8cd0..0000000
--- a/src/main/java/system/ConfigSetup.java
+++ /dev/null
@@ -1,128 +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 modes.Simulation;
-import trading.BuySell;
-import trading.Currency;
-import trading.CurrentAPI;
-import trading.Trade;
-
-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 = CurrentAPI.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":
- Simulation.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":
- BuySell.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/InstanceEndpoint.java b/src/main/java/system/InstanceEndpoint.java
new file mode 100644
index 0000000..8342b2a
--- /dev/null
+++ b/src/main/java/system/InstanceEndpoint.java
@@ -0,0 +1,37 @@
+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) {
+ Socket socket = ss.accept();
+ if (socket.isConnected()) {
+ try (DataInputStream in = new DataInputStream(socket.getInputStream());
+ DataOutputStream out = new DataOutputStream(socket.getOutputStream())) {
+ out.writeUTF(config.toJson());
+ 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/system/Main.java b/src/main/java/system/Main.java
index 25ecbcf..40030c2 100644
--- a/src/main/java/system/Main.java
+++ b/src/main/java/system/Main.java
@@ -1,223 +1,28 @@
package system;
-import modes.*;
-import modes.Collection;
-import trading.*;
-import trading.Currency;
-
import java.util.*;
public class Main {
- private static List currencies;
-
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().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();
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");
- 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");
+ System.out.println("Welcome to TradeBot (v0.11.0)");
-
- 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;
- }
- BuySell.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;
- }
- BuySell.close(localAccount.getActiveTrades().get(closeIndex - 1));
- break;
- case "close all":
- localAccount.getActiveTrades().forEach(BuySell::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;
- }
- }
+ //TODO: Implement CLI interface to create and monitor instances
}
}
\ 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/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..d098268 100644
--- a/src/main/java/trading/Currency.java
+++ b/src/main/java/trading/Currency.java
@@ -1,17 +1,15 @@
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 indicators.DBB;
+import data.config.IndicatorConfig;
+import data.price.PriceBean;
+import data.price.PriceReader;
import indicators.Indicator;
-import indicators.MACD;
-import indicators.RSI;
-import system.ConfigSetup;
+import system.BinanceAPI;
+import data.config.Config;
import system.Formatter;
-import system.Mode;
import java.io.Closeable;
import java.io.File;
@@ -20,14 +18,16 @@
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.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
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;
private long candleTime;
private final List indicators = new ArrayList<>();
@@ -43,26 +43,25 @@ public class Currency implements Closeable {
private Closeable apiListener;
//Used for SIMULATION and LIVE
- public Currency(String coin) {
- this.pair = coin + ConfigSetup.getFiat();
+ public Currency(String coin, LocalAccount account) {
+ this.pair = coin + account.getInstance().getFiat();
+ this.account = account;
//Every currency needs to contain and update our indicators
- List history = CurrentAPI.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 history = BinanceAPI.get().getCandlestickBars(pair, CandlestickInterval.FIVE_MINUTES);
+ 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();
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,29 +81,32 @@ public Currency(String coin) {
}
//Used for BACKTESTING
- public Currency(String pair, String filePath) {
- this.pair = pair;
- try (PriceReader reader = new PriceReader(filePath)) {
+ public Currency(PriceReader reader, LocalAccount account) {
+ this.pair = reader.getPair();
+ this.account = account;
+ }
+
+ public CompletableFuture runBacktest(PriceReader reader) {
+ try (reader) {
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();
}
-
} catch (IOException e) {
e.printStackTrace();
}
+ return CompletableFuture.completedFuture(null);
}
private void accept(PriceBean bean) {
@@ -118,8 +120,8 @@ 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) + " ");
+ if (account.getInstance().getMode().equals(Instance.Mode.BACKTESTING)) {
+ appendLogLine(system.Formatter.formatDate(currentTime) + " " + this);
}
}
@@ -127,13 +129,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 ((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 && BuySell.enoughFunds()) {
- BuySell.open(Currency.this, "Trade opened due to: " + getExplanations());
+ } else if (confluence >= Config.get(this).getConfluenceToOpen() && account.enoughFunds()) {
+ account.open(Currency.this, "Trade opened due to: " + getExplanations());
}
currentlyCalculating.set(false);
}
@@ -181,12 +183,16 @@ 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");
- writer.write(ConfigSetup.getSetup());
+ 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));
@@ -211,8 +217,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) {
@@ -264,7 +270,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/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/Instance.java b/src/main/java/trading/Instance.java
new file mode 100644
index 0000000..5b4a726
--- /dev/null
+++ b/src/main/java/trading/Instance.java
@@ -0,0 +1,332 @@
+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 data.config.ConfigData;
+import data.price.PriceReader;
+import system.BinanceAPI;
+import data.config.Config;
+import system.Formatter;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+import java.util.function.Function;
+
+public class Instance implements Closeable {
+ 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;
+ }
+
+ public LocalAccount getAccount() {
+ return account;
+ }
+
+ public Mode getMode() {
+ return mode;
+ }
+
+
+ @Override
+ public void close() {
+ for (Currency currency : currencies) {
+ try {
+ currency.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * 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.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 {
+ currencies.add(new Currency(arg, account));
+ } catch (BinanceApiException e) {
+ System.out.println("---Could not add " + arg + fiat);
+ System.out.println(e.getMessage());
+ }
+ }
+ }
+
+ //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);
+
+ 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);
+ }
+ 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");
+ }
+ 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 : BinanceAPI.getAccount().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 + 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());
+ 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 + fiat);
+ System.out.println(e.getMessage());
+ }
+ }
+
+ public void refreshWalletAndTrades() {
+ if (mode != Mode.LIVE) return;
+ 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());
+ 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() + 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());
+ 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()) + " " + 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(instance.getFiat(), "")
+ + " (" + Formatter.formatDecimal(entry.getKey().getPrice() * entry.getValue()) + " " + instance.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 ConfigData getConfig() {
+ return config.getData();
+ }
+
+ public String getFiat() {
+ return fiat;
+ }
+
+ public enum Mode {
+ LIVE,
+ SIMULATION,
+ BACKTESTING;
+ }
+}
diff --git a/src/main/java/trading/LocalAccount.java b/src/main/java/trading/LocalAccount.java
index 99d7b21..d221dc7 100644
--- a/src/main/java/trading/LocalAccount.java
+++ b/src/main/java/trading/LocalAccount.java
@@ -1,19 +1,28 @@
package trading;
-import com.binance.api.client.domain.account.Account;
+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.BinanceAPI;
import system.Formatter;
+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 {
- private final String username;
- private Account realAccount;
+ private final Instance instance;
//To give the account a specific final amount of money.
private double fiatValue;
@@ -21,51 +30,21 @@ public class LocalAccount {
private final ConcurrentHashMap wallet;
private final List tradeHistory;
private final List activeTrades;
- private double makerComission;
- private double takerComission;
- private double buyerComission;
- /**
- * 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<>();
- tradeHistory = new ArrayList<>();
- activeTrades = new CopyOnWriteArrayList<>();
- }
+ 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());
- public LocalAccount(String apiKey, String secretApiKey) {
- CurrentAPI.login(apiKey, secretApiKey);
- username = "";
wallet = new ConcurrentHashMap<>();
tradeHistory = new ArrayList<>();
activeTrades = new CopyOnWriteArrayList<>();
- realAccount = CurrentAPI.get().getAccount();
- if (!realAccount.isCanTrade()) {
- System.out.println("Can't trade!");
- }
- makerComission = 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
- // liquidity from our order book by placing any order that is executed against an order on the order book.
- buyerComission = 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());
- }
-
- public Account getRealAccount() {
- return realAccount;
}
//All backend.Trade methods
@@ -90,15 +69,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;
}
@@ -161,15 +139,167 @@ public void removeFromWallet(Currency key, double value) {
wallet.put(key, wallet.get(key) - value);
}
- public double getMakerComission() {
- return makerComission;
+ 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 (instance.getMode().equals(Instance.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) + " " + instance.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 (instance.getMode().equals(Instance.Mode.BACKTESTING)) currency.appendLogLine(message);
+ }
+
+ //Used by trade
+ public void close(Trade trade) {
+ if (instance.getMode().equals(Instance.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) + " " + instance.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 (instance.getMode().equals(Instance.Mode.BACKTESTING)) trade.getCurrency().appendLogLine(message);
}
- public double getTakerComission() {
- return takerComission;
+ private double nextAmount() {
+ if (instance.getMode().equals(Instance.Mode.BACKTESTING)) return getFiat();
+ return Math.min(getFiat(), getTotalValue() * instance.getConfig().getMoneyPerTrade());
}
- public double getBuyerComission() {
- return buyerComission;
+
+ //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 = BinanceAPI.get().getExchangeInfo().getSymbolInfo(currency.getPair()).getBaseAssetPrecision();
+ String lotSize;
+ 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 {
+ 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 = BinanceAPI.get().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..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,27 +91,30 @@ 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";
- BuySell.close(this);
+ 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";
- BuySell.close(this);
+ 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;
- 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"