From 13d5f99a55c7886c6f937c8b180be483ae725977 Mon Sep 17 00:00:00 2001 From: clarkohw Date: Tue, 13 Apr 2021 23:10:33 -0400 Subject: added profit-calc for hub integration --- src/main/java/edu/brown/cs/student/term/Main.java | 43 +++---- .../brown/cs/student/term/ProfitCalculation.java | 126 +++++++++++++-------- 2 files changed, 101 insertions(+), 68 deletions(-) (limited to 'src') diff --git a/src/main/java/edu/brown/cs/student/term/Main.java b/src/main/java/edu/brown/cs/student/term/Main.java index 7b9c9fc..08cadd7 100644 --- a/src/main/java/edu/brown/cs/student/term/Main.java +++ b/src/main/java/edu/brown/cs/student/term/Main.java @@ -46,6 +46,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.*; + import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import joptsimple.OptionParser; @@ -65,6 +66,7 @@ import freemarker.template.Configuration; import org.json.JSONObject; + import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; @@ -130,7 +132,7 @@ public final class Main { return new FreeMarkerEngine(config); } - public void runSparkServer(int port) { + public void runSparkServer(int port) { Spark.port(port); Spark.externalStaticFileLocation("src/main/resources/static"); Spark.exception(Exception.class, new ExceptionPrinter()); @@ -138,26 +140,26 @@ public final class Main { Spark.options("/*", (request, response) -> { - String accessControlRequestHeaders = request - .headers("Access-Control-Request-Headers"); - if (accessControlRequestHeaders != null) { - response.header("Access-Control-Allow-Headers", - accessControlRequestHeaders); - } - - String accessControlRequestMethod = request - .headers("Access-Control-Request-Method"); - if (accessControlRequestMethod != null) { - response.header("Access-Control-Allow-Methods", - accessControlRequestMethod); - } - - return "OK"; + String accessControlRequestHeaders = request + .headers("Access-Control-Request-Headers"); + if (accessControlRequestHeaders != null) { + response.header("Access-Control-Allow-Headers", + accessControlRequestHeaders); + } + + String accessControlRequestMethod = request + .headers("Access-Control-Request-Method"); + if (accessControlRequestMethod != null) { + response.header("Access-Control-Allow-Methods", + accessControlRequestMethod); + } + + return "OK"; }); - + Spark.before((request, response) -> response.header("Access-Control-Allow-Origin", "*")); - Spark.post("/data", new DataHandler()); + Spark.post("/data", new DataHandler()); } @@ -167,7 +169,7 @@ public final class Main { String str = request.body(); xmlLinks = new JSONObject(str); //this is all the filedAt times and xml files - try{ + try { DatabaseQuerier db = new DatabaseQuerier("data/trades.sqlite3"); LinkMapper lm = new LinkMapper(db); @@ -178,7 +180,7 @@ public final class Main { HubSearch hub = new HubSearch(lm); Map him = hub.runHubSearch(start, end); return GSON.toJson(him); - } catch(Exception e){ + } catch (Exception e) { System.out.println("DBQuerier Test, couldn't connect to db???"); return "Error"; } @@ -187,7 +189,6 @@ public final class Main { /** * Display an error page when an exception occurs in the server. - * */ private static class ExceptionPrinter implements ExceptionHandler { @Override diff --git a/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java b/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java index aa1bc09..19b439e 100644 --- a/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java +++ b/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java @@ -2,17 +2,12 @@ package edu.brown.cs.student.term; import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.ProtocolException; import java.net.URI; -import java.net.URL; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; @@ -24,7 +19,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.LinkedList; -import java.util.List; import java.util.HashMap; import java.util.Map; @@ -51,6 +45,9 @@ public class ProfitCalculation { //map of stock to gains from increases in value of holdings private Map unrealizedGainsMap; + //map to store current prices of stocks -- to avoid repeated api calls + private Map currentStockPrices; + private double moneyInput; /** @@ -70,16 +67,16 @@ public class ProfitCalculation { sellHistoryMap = new HashMap<>(); realizedGainsMap = new HashMap<>(); unrealizedGainsMap = new HashMap<>(); + currentStockPrices = new HashMap<>(); } /** - * This method fills the maps of sell and buy orders with lists of oldest - new trades + * This method fills the maps of sell and buy orders with lists of oldest - new trades. */ private void organizeOrders() { //get a list of trades for a person to consider try { PreparedStatement prep; - // TODO: add start and end time prep = conn.prepareStatement("SELECT * FROM \'trades\' WHERE holder_name= ? " + " AND trade_timestamp BETWEEN ? AND ?" @@ -92,7 +89,7 @@ public class ProfitCalculation { while (rs.next()) { String ticker = rs.getString("stock_name"); int shares = rs.getInt("number_of_shares"); - double price = rs.getDouble("price"); + double price = rs.getDouble("share_price"); OrderTuple order = new OrderTuple(shares, price, rs.getDate("trade_timestamp")); //one element list for first time ticker is seen. @@ -107,9 +104,7 @@ public class ProfitCalculation { } else { buyHistoryMap.put(ticker, oneElement); } - } - //for sell orders build up sell history - else { + } else { //ignore sell orders for which we do not have buys for if (buyHistoryMap.containsKey(ticker)) { if (sellHistoryMap.containsKey(ticker)) { @@ -128,7 +123,7 @@ public class ProfitCalculation { } /** - * This method processes the sell orders in the sellHistoryMap to get realized gains + * This method processes the sell orders in the sellHistoryMap to get realized gains. */ private void getRealizedGains() { for (String ticker : sellHistoryMap.keySet()) { @@ -177,7 +172,7 @@ public class ProfitCalculation { } /** - * get the change in value of stocks which are still held + * get the change in value of stocks which are still held. */ private void getUnrealizedGains() { @@ -186,16 +181,17 @@ public class ProfitCalculation { double unrealizedGains = 0; double currentPrice = getCurrentPrice(ticker); - LinkedList stockHistory = buyHistoryMap.get(ticker); - for (OrderTuple order : stockHistory) { - unrealizedGains += order.getShares() * (currentPrice - order.getCost()); + if (currentPrice != -1) { + LinkedList stockHistory = buyHistoryMap.get(ticker); + for (OrderTuple order : stockHistory) { + unrealizedGains += order.getShares() * (currentPrice - order.getCost()); + } } - unrealizedGainsMap.put(ticker, unrealizedGains); } } - private class OrderTuple { + private final class OrderTuple { private int shares; private double cost; private Date date; @@ -224,30 +220,43 @@ public class ProfitCalculation { } private double getCurrentPrice(String ticker) { - String PRICE_URL = BASE_URL + "/last/stocks/" + ticker; + if (currentStockPrices.containsKey(ticker)) { + return currentStockPrices.get(ticker); + } else { + String PRICE_URL = BASE_URL + "/last/stocks/" + ticker; + + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(PRICE_URL)).setHeader("APCA-API-KEY-ID", API_KEY) + .setHeader("APCA-API-SECRET-KEY", SECRET_KEY) + .build(); + + HttpResponse response = null; + try { + response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(PRICE_URL)).setHeader("APCA-API-KEY-ID", API_KEY) - .setHeader("APCA-API-SECRET-KEY", SECRET_KEY) - .build(); - HttpResponse response = null; - try { - response = client.send(request, - HttpResponse.BodyHandlers.ofString()); - } catch (IOException e) { - e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); + JSONObject object = new JSONObject(response.body()); + try { + double price = object.getJSONObject("last").getDouble("price"); + currentStockPrices.put(ticker, price); + return price; + } catch (JSONException e) { + currentStockPrices.put(ticker, -1.0); + return -1.0; + } } - JSONObject object = new JSONObject(response.body()); - return object.getJSONObject("last").getDouble("price"); } - public void calculateGains() { + public double calculateGains() { organizeOrders(); getRealizedGains(); getUnrealizedGains(); @@ -261,15 +270,14 @@ public class ProfitCalculation { for (double value : unrealizedGainsMap.values()) { unrealizedGains += value; } - - double totalGains = unrealizedGains + realizedGains; - - System.out.println("Money In: " + moneyInput); - System.out.println("Money Out: " + (moneyInput + totalGains)); - System.out.println("NASDAQ on money In: " + (moneyInput * compareToSP500())); - System.out.println( - "Total: " + totalGains + "| unrealized: " + unrealizedGains + " | realized: " + - realizedGains); + return unrealizedGains + realizedGains; + +// System.out.println("Money In: " + moneyInput); +// System.out.println("Money Out: " + (moneyInput + totalGains)); +// System.out.println("NASDAQ on money In: " + (moneyInput * compareToSP500())); +// System.out.println( +// "Total: " + totalGains + "| unrealized: " + unrealizedGains + " | realized: " + +// realizedGains); } /** @@ -305,10 +313,34 @@ public class ProfitCalculation { double endPrice = object.getJSONObject(object.length() - 1).getDouble("c"); //get percent change //end - start /start - return 1 + ((endPrice - startPrice) /startPrice); + return 1 + ((endPrice - startPrice) / startPrice); } + /** + * get a map for all people in the timeframe of holder_id to percent gain. + * + * @return a map of holder_id to percent gain + */ + public Map getProfitMap() { + Map profitMap = new HashMap<>(); + try { + PreparedStatement prep; + prep = + conn.prepareStatement("SELECT * from trades group by holder_name;"); + ResultSet rs = prep.executeQuery(); + while (rs.next()) { + int id = rs.getInt("holder_id"); + this.person = rs.getString("holder_name"); + profitMap.put(id, this.calculateGains() / moneyInput); + } + } catch (SQLException throwables) { + System.out.println("ERROR: SQl error in profit calculation"); + } + System.out.println(profitMap.toString()); + return profitMap; + } + public void setConnection(String filename) throws SQLException, ClassNotFoundException { // Initialize the database connection, turn foreign keys on -- cgit v1.2.3-70-g09d2 From 0a7fddc6e29d3519c98a87408d751978c0d85bf6 Mon Sep 17 00:00:00 2001 From: clarkohw Date: Thu, 15 Apr 2021 11:04:32 -0400 Subject: added part of handler for profit --- src/main/java/edu/brown/cs/student/term/Main.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'src') diff --git a/src/main/java/edu/brown/cs/student/term/Main.java b/src/main/java/edu/brown/cs/student/term/Main.java index 08cadd7..55efbce 100644 --- a/src/main/java/edu/brown/cs/student/term/Main.java +++ b/src/main/java/edu/brown/cs/student/term/Main.java @@ -160,6 +160,7 @@ public final class Main { Spark.before((request, response) -> response.header("Access-Control-Allow-Origin", "*")); Spark.post("/data", new DataHandler()); + Spark.post("/profit", new ProfitQueryHandler()); } @@ -187,6 +188,24 @@ public final class Main { } } + private static class ProfitQueryHandler implements Route { + @Override + public Object handle(Request request, Response response) throws Exception { + JSONObject req = new JSONObject(request.body()); + String person = req.getString("person"); + Date startPeriod = new Date(req.getLong("startDate")); + Date endPeriod = new Date(req.getLong("endDate")); + + try { + DatabaseQuerier db = new DatabaseQuerier("data/trades.sqlite3"); + new ProfitCalculation(DatabaseQuerier.getConn(), "person", startPeriod, endPeriod); + } catch (Exception e) { + System.out.println("DBQuerier Test, couldn't connect to db???"); + return "Error"; + } + } + } + /** * Display an error page when an exception occurs in the server. */ -- cgit v1.2.3-70-g09d2 From 61c3b3eae45221137d81112cd17ed708054e2756 Mon Sep 17 00:00:00 2001 From: Julia McCauley Date: Thu, 15 Apr 2021 11:09:57 -0400 Subject: full suspicion rank calculations working, made endpoint to request holders and their sus ranks --- src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java b/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java index a1196d8..8af9513 100644 --- a/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java +++ b/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java @@ -1,4 +1,6 @@ package edu.brown.cs.student.term.hub; public class SuspicionRanker { + + } -- cgit v1.2.3-70-g09d2 From 07ec82031f77e43e6c302cff08f8e450b16cd714 Mon Sep 17 00:00:00 2001 From: Julia McCauley Date: Thu, 15 Apr 2021 11:10:44 -0400 Subject: made the suspicion rank calculations and added an endpoint to access the list of holders with their ranks# --- .../edu/brown/cs/student/term/DatabaseQuerier.java | 6 ++ src/main/java/edu/brown/cs/student/term/Main.java | 93 +++++++--------------- .../java/edu/brown/cs/student/term/hub/Holder.java | 7 ++ .../edu/brown/cs/student/term/hub/HubSearch.java | 2 - .../edu/brown/cs/student/term/hub/LinkMapper.java | 1 + .../brown/cs/student/term/hub/SuspicionRanker.java | 43 ++++++++++ .../student/term/repl/commands/SetupCommand.java | 28 ------- 7 files changed, 85 insertions(+), 95 deletions(-) (limited to 'src') diff --git a/src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java b/src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java index f4be9b3..3cb6c79 100644 --- a/src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java +++ b/src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java @@ -49,6 +49,9 @@ public class DatabaseQuerier{ while(rs.next()){ stocks.add(rs.getString(1)); } + + rs.close(); + prep.close(); return stocks; } @@ -84,6 +87,9 @@ public class DatabaseQuerier{ rs.getDouble(8))); } + rs.close(); + prep.close(); + return trades; } } diff --git a/src/main/java/edu/brown/cs/student/term/Main.java b/src/main/java/edu/brown/cs/student/term/Main.java index 08cadd7..97f4425 100644 --- a/src/main/java/edu/brown/cs/student/term/Main.java +++ b/src/main/java/edu/brown/cs/student/term/Main.java @@ -1,8 +1,10 @@ package edu.brown.cs.student.term; +import com.google.common.collect.ImmutableMap; import edu.brown.cs.student.term.hub.Holder; import edu.brown.cs.student.term.hub.HubSearch; import edu.brown.cs.student.term.hub.LinkMapper; +import edu.brown.cs.student.term.hub.SuspicionRanker; import edu.brown.cs.student.term.repl.Command; import edu.brown.cs.student.term.repl.REPL; import edu.brown.cs.student.term.repl.commands.LoadCommand; @@ -10,30 +12,12 @@ import edu.brown.cs.student.term.repl.commands.SetupCommand; import joptsimple.OptionParser; import joptsimple.OptionSet; -import java.sql.Connection; -import java.sql.Date; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.sql.Statement; import java.time.Instant; import java.util.HashMap; -import java.io.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import joptsimple.OptionParser; -import joptsimple.OptionSet; import spark.*; import spark.template.freemarker.FreeMarkerEngine; - -import java.io.BufferedReader; -import java.io.InputStreamReader; - -import com.google.common.collect.ImmutableMap; - import freemarker.template.Configuration; //fix @@ -41,46 +25,19 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; -import java.sql.ResultSet; -import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.*; -import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; -import joptsimple.OptionParser; -import joptsimple.OptionSet; -import spark.ExceptionHandler; -import spark.ModelAndView; -import spark.QueryParamsMap; -import spark.Request; -import spark.Response; -import spark.Route; -import spark.Filter; -import spark.Spark; -import spark.TemplateViewRoute; -import spark.template.freemarker.FreeMarkerEngine; - -import freemarker.template.Configuration; - import org.json.JSONObject; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.sql.Statement; - /** * The Main class of our project. This is where execution begins. */ public final class Main { - // TODO: fix temproary solution public static JSONObject xmlLinks = null; - - private static final Gson GSON = new Gson(); private static final int DEFAULT_PORT = 4567; @@ -109,6 +66,9 @@ public final class Main { if (options.has("gui")) { runSparkServer((int) options.valueOf("port")); + //will auto connect to correct db when running gui! + SetupCommand setConnection = new SetupCommand(); + setConnection.run(new String[]{"data/trades.sqlite3"}); } HashMap commandHashMap = new HashMap<>(); @@ -156,30 +116,35 @@ public final class Main { return "OK"; }); - - Spark.before((request, response) -> response.header("Access-Control-Allow-Origin", "*")); - Spark.post("/data", new DataHandler()); + Spark.post("/data", new SuspicionRankHandler()); } - - private static class DataHandler implements Route { + /** + * Gets the list of holders with id, name, and suspicion rank + */ + private static class SuspicionRankHandler implements Route { @Override + /** + * Expects that the request will contain two longs that are the start/end + * dates for the suspicion rank to run on as epoch time in milliseconds + */ public Object handle(Request request, Response response) throws Exception { - String str = request.body(); - xmlLinks = new JSONObject(str); //this is all the filedAt times and xml files - + //String str = request.body(); + //xmlLinks = new JSONObject(str); //this is all the filedAt times and xml files try { - DatabaseQuerier db = new DatabaseQuerier("data/trades.sqlite3"); - LinkMapper lm = new LinkMapper(db); - - Instant start = Instant.parse("2021-03-30T05:00:00.00Z"); - //12 am on 3/28 in UTC - Instant end = Instant.parse("2021-04-12T05:00:00.00Z"); - lm.makeFollowerLinks(start, end); - HubSearch hub = new HubSearch(lm); - Map him = hub.runHubSearch(start, end); - return GSON.toJson(him); + DatabaseQuerier db = SetupCommand.getDq(); + SuspicionRanker ranker = new SuspicionRanker(db); + + JSONObject data = new JSONObject(request.body()); + + long startMilli = data.getLong("start"); + long endMilli = data.getLong("end"); + Instant start = Instant.ofEpochMilli(startMilli); + Instant end = Instant.ofEpochMilli(endMilli); + List suspiciousHolders = ranker.getSuspicionScoreList(start, end); + Map variables = ImmutableMap.of("holders", suspiciousHolders); + return GSON.toJson(variables); } catch (Exception e) { System.out.println("DBQuerier Test, couldn't connect to db???"); return "Error"; @@ -203,6 +168,4 @@ public final class Main { res.body(stacktrace.toString()); } } - - } \ No newline at end of file diff --git a/src/main/java/edu/brown/cs/student/term/hub/Holder.java b/src/main/java/edu/brown/cs/student/term/hub/Holder.java index abe59df..66def5e 100644 --- a/src/main/java/edu/brown/cs/student/term/hub/Holder.java +++ b/src/main/java/edu/brown/cs/student/term/hub/Holder.java @@ -5,6 +5,7 @@ import java.util.Objects; public class Holder { private int id; private String name; + private double suspicionScore; public Holder(int id, String name){ this.id = id; @@ -15,6 +16,12 @@ public class Holder { return id; } + public void setSuspicionScore(double sus){ + this.suspicionScore = sus; + } + + public double getSuspicionScore(){return suspicionScore;} + public String getName() { return name; } diff --git a/src/main/java/edu/brown/cs/student/term/hub/HubSearch.java b/src/main/java/edu/brown/cs/student/term/hub/HubSearch.java index 4d5755c..baee5af 100644 --- a/src/main/java/edu/brown/cs/student/term/hub/HubSearch.java +++ b/src/main/java/edu/brown/cs/student/term/hub/HubSearch.java @@ -11,12 +11,10 @@ public class HubSearch { LinkMapper mapper; Map> followerToLeaderMap = new HashMap<>(); - //TODO: Make this just take in a map from holder -> set of holder public HubSearch(LinkMapper mapper){ this.mapper = mapper; } - //TODO: reevaluate this basic version and tweak to customize public Map runHubSearch(Instant start, Instant end){ followerToLeaderMap = mapper.makeFollowerLinks(start, end); int numHolders = followerToLeaderMap.size(); diff --git a/src/main/java/edu/brown/cs/student/term/hub/LinkMapper.java b/src/main/java/edu/brown/cs/student/term/hub/LinkMapper.java index 8bc8cf9..f4bc2c4 100644 --- a/src/main/java/edu/brown/cs/student/term/hub/LinkMapper.java +++ b/src/main/java/edu/brown/cs/student/term/hub/LinkMapper.java @@ -54,6 +54,7 @@ public class LinkMapper { return followerToLeaders; } + //leader => follower => List of trades in common //TODO: Try to create both leader => follower and follower => leader map at once //only if necessary tho! diff --git a/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java b/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java index 8af9513..7cb7e32 100644 --- a/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java +++ b/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java @@ -1,6 +1,49 @@ package edu.brown.cs.student.term.hub; +import edu.brown.cs.student.term.DatabaseQuerier; +import edu.brown.cs.student.term.ProfitCalculation; +import edu.brown.cs.student.term.hub.Holder; +import edu.brown.cs.student.term.hub.HubSearch; +import edu.brown.cs.student.term.hub.LinkMapper; +import edu.brown.cs.student.term.repl.Command; + +import java.sql.SQLException; +import java.time.Instant; +import java.sql.Date; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + public class SuspicionRanker { + DatabaseQuerier querier; + public SuspicionRanker(DatabaseQuerier db){ + this.querier = db; + } + + public List getSuspicionScoreList(Instant start, Instant end){ + List suspicionList = new ArrayList<>(); + try { + LinkMapper lm = new LinkMapper(querier); + HubSearch hub = new HubSearch(lm); + Map holderToHubScore = hub.runHubSearch(start, end); + + ProfitCalculation pc = new ProfitCalculation(DatabaseQuerier.getConn(), "", + new Date(start.toEpochMilli()), + new Date(end.toEpochMilli())); + + Map profitMap = pc.getProfitMap(); + for(Holder guy: holderToHubScore.keySet()){ + Double profitScore = profitMap.get(guy.getId()); + Double suspicionScore = (holderToHubScore.get(guy)*0.2 + + profitScore * 0.8) * 100; + guy.setSuspicionScore(suspicionScore); + suspicionList.add(guy); + } + } catch (Exception e) { + System.out.println("ERROR: Could not connect to database querier"); + } + return suspicionList; + } } diff --git a/src/main/java/edu/brown/cs/student/term/repl/commands/SetupCommand.java b/src/main/java/edu/brown/cs/student/term/repl/commands/SetupCommand.java index e19117c..a6ef1f0 100644 --- a/src/main/java/edu/brown/cs/student/term/repl/commands/SetupCommand.java +++ b/src/main/java/edu/brown/cs/student/term/repl/commands/SetupCommand.java @@ -34,34 +34,6 @@ public class SetupCommand implements Command { System.out.println(error); return error; } - - try{ - /** Just for testing purposes **/ - //12 am on 3/12 in UTC - Instant start = Instant.parse("2021-03-12T05:00:00.00Z"); - //12 am on 3/27 in UTC - Instant end = Instant.parse("2021-03-27T05:00:00.00Z"); - - System.out.println(end.toEpochMilli()); - System.out.println(start.getEpochSecond()); - - ZonedDateTime zdt = ZonedDateTime.ofInstant(start, ZoneId.of("America/New_York")); - System.out.println(zdt.toString()); - List> trades = dq.getAllTradesByStock(start, end); - - int sum = 0; - for(List t: trades){ - System.out.println(t); - sum += t.size(); - } - - System.out.println("num of trades: " + sum); - - - } catch(Exception e){ - e.printStackTrace(); - } - return error; } -- cgit v1.2.3-70-g09d2 From 1301a143b3b0e89180883c9948b13a1700f8821f Mon Sep 17 00:00:00 2001 From: clarkohw Date: Thu, 15 Apr 2021 19:58:14 -0400 Subject: added endpoint --- src/main/java/edu/brown/cs/student/term/Main.java | 28 +++++++++-- .../brown/cs/student/term/ProfitCalculation.java | 56 +++++++++++++++++----- .../brown/cs/student/term/profit/StockHolding.java | 16 +++++++ 3 files changed, 85 insertions(+), 15 deletions(-) create mode 100644 src/main/java/edu/brown/cs/student/term/profit/StockHolding.java (limited to 'src') diff --git a/src/main/java/edu/brown/cs/student/term/Main.java b/src/main/java/edu/brown/cs/student/term/Main.java index 55efbce..0a70b91 100644 --- a/src/main/java/edu/brown/cs/student/term/Main.java +++ b/src/main/java/edu/brown/cs/student/term/Main.java @@ -3,6 +3,7 @@ package edu.brown.cs.student.term; import edu.brown.cs.student.term.hub.Holder; import edu.brown.cs.student.term.hub.HubSearch; import edu.brown.cs.student.term.hub.LinkMapper; +import edu.brown.cs.student.term.profit.StockHolding; import edu.brown.cs.student.term.repl.Command; import edu.brown.cs.student.term.repl.REPL; import edu.brown.cs.student.term.repl.commands.LoadCommand; @@ -193,16 +194,35 @@ public final class Main { public Object handle(Request request, Response response) throws Exception { JSONObject req = new JSONObject(request.body()); String person = req.getString("person"); - Date startPeriod = new Date(req.getLong("startDate")); - Date endPeriod = new Date(req.getLong("endDate")); - + Date startPeriod = new Date(req.getLong("startTime")); + Date endPeriod = new Date(req.getLong("endTime")); + + List holdings = new LinkedList<>(); + ProfitCalculation profit; + double gains = 0.0; + double sp500PercentGain = 0.0; + double sp500Gain = 0.0; try { DatabaseQuerier db = new DatabaseQuerier("data/trades.sqlite3"); - new ProfitCalculation(DatabaseQuerier.getConn(), "person", startPeriod, endPeriod); + profit = + new ProfitCalculation(DatabaseQuerier.getConn(), person, startPeriod, endPeriod); + holdings = profit.getHoldingsList(); + gains = profit.calculateGains(); + sp500PercentGain = profit.compareToSP500(); } catch (Exception e) { System.out.println("DBQuerier Test, couldn't connect to db???"); return "Error"; } + + Map res = new HashMap(); + res.put("person", person); + res.put("moneyIn", profit.getMoneyInput()); + res.put("moneyOut", profit.getMoneyInput() + gains); + res.put("holdings", holdings); + res.put("percentGain", 100 * gains / profit.getMoneyInput()); + res.put("SP500", (1 + sp500PercentGain) * profit.getMoneyInput()); + res.put("percentSP500", 100 * sp500PercentGain); + return GSON.toJson(res); } } diff --git a/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java b/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java index 19b439e..28f7c79 100644 --- a/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java +++ b/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java @@ -1,6 +1,8 @@ package edu.brown.cs.student.term; +import edu.brown.cs.student.term.profit.StockHolding; +import org.eclipse.jetty.util.DecoratedObjectFactory; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -20,6 +22,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.LinkedList; import java.util.HashMap; +import java.util.List; import java.util.Map; public class ProfitCalculation { @@ -27,6 +30,7 @@ public class ProfitCalculation { private String person; private Date startTime; private Date endTime; + private boolean tablesFilled; private String BASE_URL = "https://data.alpaca.markets/v1"; @@ -68,6 +72,7 @@ public class ProfitCalculation { realizedGainsMap = new HashMap<>(); unrealizedGainsMap = new HashMap<>(); currentStockPrices = new HashMap<>(); + tablesFilled = false; } /** @@ -257,9 +262,12 @@ public class ProfitCalculation { } public double calculateGains() { - organizeOrders(); - getRealizedGains(); - getUnrealizedGains(); + if (!tablesFilled) { + organizeOrders(); + getRealizedGains(); + getUnrealizedGains(); + tablesFilled = true; + } double realizedGains = 0; double unrealizedGains = 0; @@ -271,19 +279,41 @@ public class ProfitCalculation { unrealizedGains += value; } return unrealizedGains + realizedGains; + } + + public List getHoldingsList() { + if (!tablesFilled) { + organizeOrders(); + getRealizedGains(); + getUnrealizedGains(); + tablesFilled = true; + } -// System.out.println("Money In: " + moneyInput); -// System.out.println("Money Out: " + (moneyInput + totalGains)); -// System.out.println("NASDAQ on money In: " + (moneyInput * compareToSP500())); -// System.out.println( -// "Total: " + totalGains + "| unrealized: " + unrealizedGains + " | realized: " + -// realizedGains); + List holdings = new LinkedList<>(); + + for (String key : buyHistoryMap.keySet()) { + double realizedGains = 0; + double unrealizedGains = 0; + if (unrealizedGainsMap.containsKey(key)) { + unrealizedGains = unrealizedGainsMap.get(key); + } + if (realizedGainsMap.containsKey(key)) { + realizedGains = realizedGainsMap.get(key); + } + + int shares = 0; + for (OrderTuple order : buyHistoryMap.get(key)) { + shares += order.getShares(); + } + holdings.add(new StockHolding(key, realizedGains, unrealizedGains, shares)); + } + return holdings; } /** * return percent change in SPY (SP 500) over the time period. */ - private double compareToSP500() { + public double compareToSP500() { String url = "https://data.alpaca.markets/v1/bars/" + "day?" + "symbols=SPY" @@ -313,7 +343,7 @@ public class ProfitCalculation { double endPrice = object.getJSONObject(object.length() - 1).getDouble("c"); //get percent change //end - start /start - return 1 + ((endPrice - startPrice) / startPrice); + return ((endPrice - startPrice) / startPrice); } @@ -341,6 +371,10 @@ public class ProfitCalculation { return profitMap; } + public double getMoneyInput() { + return this.moneyInput; + } + public void setConnection(String filename) throws SQLException, ClassNotFoundException { // Initialize the database connection, turn foreign keys on diff --git a/src/main/java/edu/brown/cs/student/term/profit/StockHolding.java b/src/main/java/edu/brown/cs/student/term/profit/StockHolding.java new file mode 100644 index 0000000..e2b174c --- /dev/null +++ b/src/main/java/edu/brown/cs/student/term/profit/StockHolding.java @@ -0,0 +1,16 @@ +package edu.brown.cs.student.term.profit; + +public class StockHolding { + private String ticker; + private Double realizedGain; + private Double unrealizedGain; + private int shares; + + public StockHolding(String ticker, Double realizedGain, Double unrealizedGain, int shares) { + this.ticker = ticker; + this.realizedGain = realizedGain; + this.unrealizedGain = unrealizedGain; + this.shares = shares; + } + +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From f42119ece5f132ef473d7c2ee5a7cfd64d83f132 Mon Sep 17 00:00:00 2001 From: Julia McCauley Date: Thu, 15 Apr 2021 21:40:42 -0400 Subject: Fixed suspicion ranks, work with real data now and added lots of unit tests --- data/empty.sqlite3 | Bin 0 -> 8192 bytes data/lil_mock.sqlite3 | Bin 8192 -> 8192 bytes data/mock_trades.sqlite3 | Bin 49152 -> 49152 bytes .../edu/brown/cs/student/term/DatabaseQuerier.java | 19 ++- src/main/java/edu/brown/cs/student/term/Main.java | 8 +- .../brown/cs/student/term/ProfitCalculation.java | 4 + .../edu/brown/cs/student/term/hub/HubSearch.java | 66 +------- .../edu/brown/cs/student/term/hub/LinkMapper.java | 16 +- .../brown/cs/student/term/hub/SuspicionRanker.java | 26 ++-- .../edu/brown/cs/student/term/repl/Command.java | 8 +- .../java/edu/brown/cs/student/term/repl/REPL.java | 1 + .../java/edu/brown/cs/student/DBQuerierTest.java | 167 +++++++++++++++++---- .../java/edu/brown/cs/student/HubRankTest.java | 66 ++++++-- .../java/edu/brown/cs/student/LinkMapperTest.java | 59 ++++++-- .../edu/brown/cs/student/SuspicionRankerTest.java | 52 +++++++ 15 files changed, 346 insertions(+), 146 deletions(-) create mode 100644 data/empty.sqlite3 create mode 100644 src/test/java/edu/brown/cs/student/SuspicionRankerTest.java (limited to 'src') diff --git a/data/empty.sqlite3 b/data/empty.sqlite3 new file mode 100644 index 0000000..3d10907 Binary files /dev/null and b/data/empty.sqlite3 differ diff --git a/data/lil_mock.sqlite3 b/data/lil_mock.sqlite3 index c326719..28b5b55 100644 Binary files a/data/lil_mock.sqlite3 and b/data/lil_mock.sqlite3 differ diff --git a/data/mock_trades.sqlite3 b/data/mock_trades.sqlite3 index 7658214..ade96f8 100644 Binary files a/data/mock_trades.sqlite3 and b/data/mock_trades.sqlite3 differ diff --git a/src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java b/src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java index 3cb6c79..688270f 100644 --- a/src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java +++ b/src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java @@ -7,7 +7,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; -public class DatabaseQuerier{ +public class DatabaseQuerier { private static Connection conn = null; //TODO: Be prepared to overhaul this to account for IDs @@ -55,7 +55,13 @@ public class DatabaseQuerier{ return stocks; } - //TODO: Fill these in + /** + * Gets all the trades in by stock and buy type, ordered by time + * @param startDate - the start date of these trades + * @param endDate - the end date of these trades + * @return a list of list of trades as specified above + * @throws SQLException - if something goes wrong with connection + */ public List> getAllTradesByStock(Instant startDate, Instant endDate) throws SQLException { List> allTrades = new ArrayList<>(); List stocks = getRecentStocks(startDate, endDate); @@ -67,6 +73,15 @@ public class DatabaseQuerier{ return allTrades; } + /** + * Gets a single stock's list of trades for that time period (either buy or sell) + * @param stock - string name of the stock to get the trades for + * @param isBuy - integer whether it's a buy or sell + * @param startDate - an Instant representing the start of the time period + * @param endDate - an Instant representing the end of the time period + * @return - a list of trades for that stock + * @throws SQLException - if issue getting connection + */ public List getTradeByStock(String stock, int isBuy, Instant startDate, Instant endDate) throws SQLException{ List trades = new ArrayList<>(); diff --git a/src/main/java/edu/brown/cs/student/term/Main.java b/src/main/java/edu/brown/cs/student/term/Main.java index b61d80f..49295f0 100644 --- a/src/main/java/edu/brown/cs/student/term/Main.java +++ b/src/main/java/edu/brown/cs/student/term/Main.java @@ -13,6 +13,7 @@ import joptsimple.OptionParser; import joptsimple.OptionSet; import java.time.Instant; +import java.sql.Date; import java.util.HashMap; import spark.*; @@ -117,12 +118,8 @@ public final class Main { return "OK"; }); Spark.before((request, response) -> response.header("Access-Control-Allow-Origin", "*")); -<<<<<<< HEAD Spark.post("/data", new SuspicionRankHandler()); -======= - Spark.post("/data", new DataHandler()); Spark.post("/profit", new ProfitQueryHandler()); ->>>>>>> 73ad5303d59cd93a115401b1bac4aad87dfb1cb7 } /** @@ -172,7 +169,10 @@ public final class Main { System.out.println("DBQuerier Test, couldn't connect to db???"); return "Error"; } + //TODO: Clark th is is just to fix a no return error + return "Temporary"; } + } /** diff --git a/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java b/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java index 19b439e..30d7f36 100644 --- a/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java +++ b/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java @@ -332,6 +332,10 @@ public class ProfitCalculation { while (rs.next()) { int id = rs.getInt("holder_id"); this.person = rs.getString("holder_name"); + //TODO: Temporary fix for the moneyinput divide by 0 error + if(moneyInput == 0){ + moneyInput = 1; + } profitMap.put(id, this.calculateGains() / moneyInput); } } catch (SQLException throwables) { diff --git a/src/main/java/edu/brown/cs/student/term/hub/HubSearch.java b/src/main/java/edu/brown/cs/student/term/hub/HubSearch.java index baee5af..ccefeef 100644 --- a/src/main/java/edu/brown/cs/student/term/hub/HubSearch.java +++ b/src/main/java/edu/brown/cs/student/term/hub/HubSearch.java @@ -52,15 +52,9 @@ public class HubSearch { //dampening factor double damp = 0.15; /* - In normal page rank: - links is the pages that k links to, if k links to j, then - we want to add some weight to j from k, but that - weight is diluted by the number of links that k has - in general because we don't want to count a bunch of links - from a page as highly as one targeted link from one page to another In Hub Search - leader = j, follower = k, if follower links to (follows) leader, + we have the leader and follower if follower links to (follows) leader, then we want to add some weight from follower to leader, but should that weight be diluted by the number of people who followed leader or the number of people who follower followed @@ -85,62 +79,4 @@ public class HubSearch { return sum <= 0.001; } - - - - - - - - - - /* - def pageRank(pList: List[Page]): mutable.HashMap[Int, Double] = { - val n = pList.length - val weights = new Array[Double](n) - val r = new Array[Double](n) - val rPrime = Array.fill[Double](n)(1.0 / n) - - while (!distance(r, rPrime)) { - Array.copy(rPrime, 0, r, 0, n) - for (j <- 0 to n - 1) { - rPrime(j) = 0 - for (k <- 0 to n - 1) { - val wjk = getWeight(pList(j), pList(k), n) - rPrime(j) = (rPrime(j) + (wjk * r(k))) - } - } - } - - val pageRank = new mutable.HashMap[Int, Double]() - - for (i <- 0 to rPrime.length - 1) { - pageRank.put(pList(i).getID, rPrime(i)) - } - pageRank - } - - def getWeight(j: Page, k: Page, n: Int): Double = { - - val links = k.getLinks - val nk = links.size - - if (links.contains(j.getTitle.toLowerCase)) { - ((0.15 / n) + ((1 - 0.15) * (1.0 / nk))) - } else if (nk == 0) { - ((0.15 / n) + ((1 - 0.15) * (1.0 / n))) - } else { - (0.15 / n) - } - } - - def distance(r1: Array[Double], r2: Array[Double]): Boolean = { - var sum = 0.0 - for (i <- 0 to r1.length - 1) { - sum = sum + Math.pow((r1(i) - r2(i)), 2) - } - sum <= .001 - - } - */ } diff --git a/src/main/java/edu/brown/cs/student/term/hub/LinkMapper.java b/src/main/java/edu/brown/cs/student/term/hub/LinkMapper.java index f4bc2c4..b490ea1 100644 --- a/src/main/java/edu/brown/cs/student/term/hub/LinkMapper.java +++ b/src/main/java/edu/brown/cs/student/term/hub/LinkMapper.java @@ -21,7 +21,7 @@ public class LinkMapper { /** - * Returns the follower => leaders map as is, does not update it + * Returns the follower to leaders map as is, does not update it * @return person to follower map */ public Map> getFollowerToLeaders() { @@ -29,9 +29,11 @@ public class LinkMapper { } /** - * The links between people and their followers who made the same trade + * This links a person and the set of people whose trades they followed * in the same time period - * @return the newly updated link map + * @param start - instant representing start of time period to look at + * @param end - instant representing end of time period to look at + * @return - the newly updated link map */ public Map> makeFollowerLinks(Instant start, Instant end){ if(databaseQuerier != null){ @@ -54,10 +56,10 @@ public class LinkMapper { return followerToLeaders; } - //leader => follower => List of trades in common - - //TODO: Try to create both leader => follower and follower => leader map at once - //only if necessary tho! + /** + * Converts a single trade list into entries in the follower to leader map + * @param tradeList - a list of trades for a single stock (either buy or sell) + */ private void convertTradeListToFollowerMap(List tradeList){ List holderList = new ArrayList<>(); diff --git a/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java b/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java index 7cb7e32..dd959f0 100644 --- a/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java +++ b/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java @@ -2,17 +2,10 @@ package edu.brown.cs.student.term.hub; import edu.brown.cs.student.term.DatabaseQuerier; import edu.brown.cs.student.term.ProfitCalculation; -import edu.brown.cs.student.term.hub.Holder; -import edu.brown.cs.student.term.hub.HubSearch; -import edu.brown.cs.student.term.hub.LinkMapper; -import edu.brown.cs.student.term.repl.Command; -import java.sql.SQLException; import java.time.Instant; import java.sql.Date; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; public class SuspicionRanker { @@ -21,6 +14,13 @@ public class SuspicionRanker { this.querier = db; } + public > V getMaxOfMap(Map map) { + Map.Entry maxEntry = Collections.max(map.entrySet(), Map.Entry.comparingByValue()); + System.out.println(maxEntry); + System.out.println(maxEntry.getValue()); + return maxEntry.getValue(); + } + public List getSuspicionScoreList(Instant start, Instant end){ List suspicionList = new ArrayList<>(); try { @@ -34,10 +34,14 @@ public class SuspicionRanker { Map profitMap = pc.getProfitMap(); + double profitMax = getMaxOfMap(profitMap); + + double hubMax = getMaxOfMap(holderToHubScore); + for(Holder guy: holderToHubScore.keySet()){ - Double profitScore = profitMap.get(guy.getId()); - Double suspicionScore = (holderToHubScore.get(guy)*0.2 - + profitScore * 0.8) * 100; + double normalizedProfitScore = profitMap.get(guy.getId()) / profitMax; + double normalizedHubScore = holderToHubScore.get(guy) / hubMax; + double suspicionScore = normalizedHubScore * 0.6 + normalizedProfitScore * 0.4; guy.setSuspicionScore(suspicionScore); suspicionList.add(guy); } diff --git a/src/main/java/edu/brown/cs/student/term/repl/Command.java b/src/main/java/edu/brown/cs/student/term/repl/Command.java index 056c7df..b110644 100644 --- a/src/main/java/edu/brown/cs/student/term/repl/Command.java +++ b/src/main/java/edu/brown/cs/student/term/repl/Command.java @@ -1,13 +1,13 @@ package edu.brown.cs.student.term.repl; /** - * Interface implemented by all five StarsCommand commands. - * Implemented by StarsCommand, NaiveNeighbors, NaiveRadiusCommand, RadiusCommand, NeighborsCommand + * Interface implemented by all the commands that the REPL is responsible for */ public interface Command { /** - * Main run method for every command. - * @param args arguments for the command + * The function that the command calls when run from the REPL + * @param args - the arguments for the command + * @return a string representation of the output, or error message */ String run(String[] args); } diff --git a/src/main/java/edu/brown/cs/student/term/repl/REPL.java b/src/main/java/edu/brown/cs/student/term/repl/REPL.java index a1d5c23..5381d45 100644 --- a/src/main/java/edu/brown/cs/student/term/repl/REPL.java +++ b/src/main/java/edu/brown/cs/student/term/repl/REPL.java @@ -16,6 +16,7 @@ public class REPL { /** * Constructor for a REPL object. + * @param userCommands - map of string to Command object for REPL to run */ public REPL(HashMap userCommands) { commands = userCommands; diff --git a/src/test/java/edu/brown/cs/student/DBQuerierTest.java b/src/test/java/edu/brown/cs/student/DBQuerierTest.java index d813969..b3ea140 100644 --- a/src/test/java/edu/brown/cs/student/DBQuerierTest.java +++ b/src/test/java/edu/brown/cs/student/DBQuerierTest.java @@ -1,21 +1,18 @@ package edu.brown.cs.student; -import java.io.PrintStream; -import java.sql.SQLException; import java.time.Instant; import java.util.ArrayList; import java.util.List; import edu.brown.cs.student.term.DatabaseQuerier; -import edu.brown.cs.student.term.repl.commands.SetupCommand; import edu.brown.cs.student.term.trade.Trade; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; -//TODO: Write more tests for methods besides stock by name public class DBQuerierTest { /** these should span the entire mock dataset */ @@ -35,12 +32,6 @@ public class DBQuerierTest { } } - /* - * try{ - - } catch(Exception e) { - System.out.println("Error in test"); - }*/ @After public void tearDown() { @@ -50,34 +41,114 @@ public class DBQuerierTest { @Test public void testNonExistentStock(){ setUp(); + List fakeStockList = new ArrayList<>(); + try{ + fakeStockList = db.getTradeByStock("NONO", 1, start, end); + } catch(Exception e) { + System.out.println("Error in test"); + } + assertTrue(fakeStockList.isEmpty()); + tearDown(); + } + + @Test + public void testEmptyDatabase(){ try{ - List fakeStockList = db.getTradeByStock("NONO", 1, start, end); - assertTrue(fakeStockList.isEmpty()); + db = new DatabaseQuerier("data/empty.sqlite3"); + } catch(Exception e){ + System.out.println("DBQuerier Test, couldn't connect to db???"); + } + + List gmeBadDatesList = new ArrayList<>(); + List noStocks = new ArrayList<>(); + List> noTrades = new ArrayList<>(); + try { + gmeBadDatesList = db.getTradeByStock("GME", 1, start, end); + noStocks = db.getRecentStocks(start, end); + noTrades = db.getAllTradesByStock(start, end); + } catch (Exception e){ + System.out.println("ERROR: in test"); + } + assertTrue(gmeBadDatesList.isEmpty()); + assertTrue(noStocks.isEmpty()); + assertTrue(noTrades.isEmpty()); + tearDown(); + } + + @Test + public void testEmptyStringStock(){ + setUp(); + List fakeStockList = new ArrayList<>(); + try{ + fakeStockList = db.getTradeByStock("", 1, start, end); } catch(Exception e) { System.out.println("Error in test"); } + assertTrue(fakeStockList.isEmpty()); tearDown(); } @Test - public void testFlippedDates(){ + public void testNeitherIsBuyOrSell(){ setUp(); + List fakeStockList = new ArrayList<>(); try{ - List gmeBadDatesList = db.getTradeByStock("GME", 1, end, start); - assertTrue(gmeBadDatesList.isEmpty()); + fakeStockList = db.getTradeByStock("GME", 3, start, end); + } catch(Exception e) { + System.out.println("Error in test"); + } + assertTrue(fakeStockList.isEmpty()); + tearDown(); + } + @Test + public void testIsSell(){ + setUp(); + List sellGMEList = new ArrayList<>(); + try{ + sellGMEList = db.getTradeByStock("GME", 0, start, end); } catch(Exception e) { System.out.println("Error in test"); } + assertEquals(7, sellGMEList.size()); + for(Trade t: sellGMEList){ + assertFalse(t.isBuy()); + } + tearDown(); + } + + + @Test + public void testFlippedDates(){ + setUp(); + List gmeBadDatesList = new ArrayList<>(); + List noStocks = new ArrayList<>(); + List> noTrades = new ArrayList<>(); + try{ + gmeBadDatesList = db.getTradeByStock("GME", 1, end, start); + noStocks = db.getRecentStocks(end, start); + noTrades = db.getAllTradesByStock(end, start); + } catch(Exception e) { + System.out.println("Error in test"); + } + assertTrue(gmeBadDatesList.isEmpty()); + assertTrue(noStocks.isEmpty()); + assertTrue(noTrades.isEmpty()); tearDown(); } @Test public void testTradeByStockNameBuy(){ setUp(); + List gmeBuyList = new ArrayList<>(); + List teslaBuyList = new ArrayList<>(); try{ - List gmeBuyList = db.getTradeByStock("GME", 1, start, end); + gmeBuyList = db.getTradeByStock("GME", 1, start, end); + teslaBuyList = db.getTradeByStock("TSLA", 1, start, end); + } catch(Exception e) { + System.out.println("Error in testTradeByStockName"); + } System.out.println(gmeBuyList); assertEquals(gmeBuyList.size(), 6); assertEquals(gmeBuyList.get(0).getId(), 482); @@ -85,16 +156,12 @@ public class DBQuerierTest { assertEquals(gmeBuyList.get(4).getId(), 275); assertEquals(gmeBuyList.get(5).getId(), 30); - - List teslaBuyList = db.getTradeByStock("TSLA", 1, start, end); assertEquals(teslaBuyList.size(), 16); assertEquals(teslaBuyList.get(0).getId(), 328); assertEquals(teslaBuyList.get(7).getId(), 241); assertEquals(teslaBuyList.get(15).getId(), 774); - } catch(Exception e) { - System.out.println("Error in testTradeByStockName"); - } + tearDown(); } @@ -103,20 +170,60 @@ public class DBQuerierTest { public void testTradeByNameOrdering(){ setUp(); + List gmeSellList = new ArrayList<>(); + List amznBuyList = new ArrayList<>(); try{ - List gmeSellList = db.getTradeByStock("GME", 0, start, end); - for(int i = 1; i < gmeSellList.size(); i++){ - assertTrue(gmeSellList.get(i-1).getTimestamp() < gmeSellList.get(i).getTimestamp()); - } + gmeSellList = db.getTradeByStock("GME", 0, start, end); + amznBuyList = db.getTradeByStock("AMZN", 1, start, end); + } catch(Exception e) { + System.out.println("Error in test"); + } - List amznBuyList = db.getTradeByStock("AMZN", 1, start, end); - for(int i = 1; i < amznBuyList.size(); i++){ - assertTrue(amznBuyList.get(i-1).getTimestamp() < amznBuyList.get(i).getTimestamp()); - } + for(int i = 1; i < gmeSellList.size(); i++){ + assertTrue(gmeSellList.get(i-1).getTimestamp() < gmeSellList.get(i).getTimestamp()); + } - } catch(Exception e) { + + for(int i = 1; i < amznBuyList.size(); i++){ + assertTrue(amznBuyList.get(i-1).getTimestamp() < amznBuyList.get(i).getTimestamp()); + } + tearDown(); + } + + @Test + public void testGetRecentStocks(){ + setUp(); + List stockNames = new ArrayList<>(); + try { + stockNames = db.getRecentStocks(start, end); + }catch(Exception e){ System.out.println("Error in test"); } + assertEquals(50, stockNames.size()); tearDown(); } + + @Test + public void testGetAllTradesByStock(){ + setUp(); + List> trades = new ArrayList<>(); + try { + trades = db.getAllTradesByStock(start, end); + }catch(Exception e){ + System.out.println("Error in test"); + } + + assertEquals(100, trades.size()); + + for(List tList: trades){ + Trade first = tList.get(0); + for(Trade t: tList){ + //all things in each list should be for the same stock and same isBuy type + assertEquals(first.isBuy(), t.isBuy()); + assertEquals(first.getStock(), t.getStock()); + } + } + tearDown(); + } + } diff --git a/src/test/java/edu/brown/cs/student/HubRankTest.java b/src/test/java/edu/brown/cs/student/HubRankTest.java index 9ba0987..cbe6112 100644 --- a/src/test/java/edu/brown/cs/student/HubRankTest.java +++ b/src/test/java/edu/brown/cs/student/HubRankTest.java @@ -7,9 +7,12 @@ import edu.brown.cs.student.term.hub.LinkMapper; import org.junit.After; import org.junit.Before; import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.time.Instant; import java.util.Map; +import java.util.Set; public class HubRankTest { @@ -30,25 +33,66 @@ public class HubRankTest { } } - /* - * try{ - - } catch(Exception e) { - System.out.println("Error in test"); - }*/ - @After public void tearDown() { db = null; } @Test - public void testMapper(){ + public void testEmptyDB(){ + try{ + db = new DatabaseQuerier("data/empty.sqlite3"); + } catch(Exception e){ + System.out.println("DBQuerier Test, couldn't connect to db???"); + } + LinkMapper lm = new LinkMapper(db); + HubSearch hub = new HubSearch(lm); + Map hubRanks = hub.runHubSearch(start, end); + assertTrue(hubRanks.isEmpty()); + tearDown(); + } + + @Test + public void flippedDates(){ + setUp(); + LinkMapper lm = new LinkMapper(db); + HubSearch hub = new HubSearch(lm); + Map hubRanks = hub.runHubSearch(end, start); + assertTrue(hubRanks.isEmpty()); + tearDown(); + + } + + @Test + public void testHubRankSmall(){ setUp(); LinkMapper lm = new LinkMapper(db); - lm.makeFollowerLinks(start, end); HubSearch hub = new HubSearch(lm); - Map him = hub.runHubSearch(start, end); - System.out.println(him); + Map hubRanks = hub.runHubSearch(start, end); + assertEquals(6, hubRanks.size()); + tearDown(); + } + + @Test + public void testCheckRightValues(){ + setUp(); + LinkMapper lm = new LinkMapper(db); + HubSearch hub = new HubSearch(lm); + Map hubRanks = hub.runHubSearch(start, end); + Holder don = new Holder(1, "Don"); + Holder mitch = new Holder(2, "Mitch"); + Holder nancy = new Holder(3, "Nancy"); + Holder midge = new Holder(4, "Midge"); + Holder bob = new Holder(5, "Bob"); + Holder jane = new Holder(6, "Jane"); + + System.out.println(hubRanks); + assertTrue(hubRanks.get(mitch) > hubRanks.get(don)); + assertTrue(hubRanks.get(don) > hubRanks.get(bob)); + assertTrue(hubRanks.get(bob) > hubRanks.get(nancy)); + assertTrue(hubRanks.get(nancy)> hubRanks.get(jane)); + assertTrue(hubRanks.get(nancy) > hubRanks.get(midge)); + + tearDown(); } } diff --git a/src/test/java/edu/brown/cs/student/LinkMapperTest.java b/src/test/java/edu/brown/cs/student/LinkMapperTest.java index 2683b90..3d4bedc 100644 --- a/src/test/java/edu/brown/cs/student/LinkMapperTest.java +++ b/src/test/java/edu/brown/cs/student/LinkMapperTest.java @@ -10,6 +10,10 @@ import org.junit.Test; import java.time.Instant; import java.util.Map; +import java.util.Set; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + public class LinkMapperTest { @@ -30,26 +34,57 @@ public class LinkMapperTest { } } - /* - * try{ - - } catch(Exception e) { - System.out.println("Error in test"); - }*/ - @After public void tearDown() { db = null; } @Test - public void testMapper(){ + public void testBasicMapper(){ + setUp(); + LinkMapper lm = new LinkMapper(db); + Map> linkMap = lm.makeFollowerLinks(start, end); + //should be one for each person in little mock (6) + assertEquals(6, linkMap.keySet().size()); + + for(Holder h: linkMap.keySet()){ + //didn't follow anyone elses trades + if(h.getName().equals("Don") || h.getName().equals("Jane")){ + assertTrue(linkMap.get(h).isEmpty()); + //biggest follower + } else if(h.getName().equals("Midge")){ + assertEquals(4, linkMap.get(h).size()); + } else if(h.getName().equals("Nancy") || h.getName().equals("Mitch")){ + assertEquals(2, linkMap.get(h).size()); + } else { + //should be Bob, only followed Mitch + assertEquals(1, linkMap.get(h).size()); + } + } + tearDown(); + } + + @Test + public void testBadDate(){ setUp(); LinkMapper lm = new LinkMapper(db); - lm.makeFollowerLinks(start, end); - HubSearch hub = new HubSearch(lm); - Map him = hub.runHubSearch(start, end); - System.out.println(him); + Map> linkMap = lm.makeFollowerLinks(end, start); + assertTrue(linkMap.isEmpty()); + tearDown(); + } + + @Test + public void testEmptyDB(){ + try{ + db = new DatabaseQuerier("data/empty.sqlite3"); + } catch(Exception e){ + System.out.println("DBQuerier Test, couldn't connect to db???"); + } + LinkMapper lm = new LinkMapper(db); + Map> linkMap = lm.makeFollowerLinks(start, end); + //should be one for each person in little mock (6) + assertTrue(linkMap.isEmpty()); + } } diff --git a/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java b/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java new file mode 100644 index 0000000..d641507 --- /dev/null +++ b/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java @@ -0,0 +1,52 @@ +package edu.brown.cs.student; + +import edu.brown.cs.student.term.DatabaseQuerier; +import edu.brown.cs.student.term.hub.Holder; +import edu.brown.cs.student.term.hub.HubSearch; +import edu.brown.cs.student.term.hub.LinkMapper; +import edu.brown.cs.student.term.hub.SuspicionRanker; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.util.List; +import java.util.Map; + +public class SuspicionRankerTest { + + //12 am on 3/11 in UTC + //private Instant start = Instant.parse("2021-03-11T05:00:00.00Z"); + private Instant start = Instant.ofEpochMilli(161800418000L); + //12 am on 3/28 in UTC + //private Instant end = Instant.parse("2021-03-28T05:00:00.00Z"); + private Instant end = Instant.ofEpochMilli(1618019436000L); + + private DatabaseQuerier db; + + @Before + public void setUp() { + try{ + db = new DatabaseQuerier("data/trades.sqlite3"); + } catch(Exception e){ + System.out.println("DBQuerier Test, couldn't connect to db???"); + } + } + + @After + public void tearDown() { + db = null; + } + + @Test + public void testMapper(){ + setUp(); + SuspicionRanker r = new SuspicionRanker(db); + List him = r.getSuspicionScoreList(start, end); + //System.out.println(him); + for(Holder guy: him){ + System.out.println(guy.getName() + " " + guy.getSuspicionScore()); + } + tearDown(); + } +} -- cgit v1.2.3-70-g09d2 From 57a91b6f728e0c2f4325690a5c8d58a3ef5332bf Mon Sep 17 00:00:00 2001 From: clarkohw Date: Fri, 16 Apr 2021 01:22:35 -0400 Subject: zero gains are recorded as 0% --- src/main/java/edu/brown/cs/student/term/Main.java | 6 +- .../brown/cs/student/term/ProfitCalculation.java | 393 --------------------- .../brown/cs/student/term/hub/SuspicionRanker.java | 13 +- .../cs/student/term/profit/ProfitCalculation.java | 393 +++++++++++++++++++++ 4 files changed, 403 insertions(+), 402 deletions(-) delete mode 100644 src/main/java/edu/brown/cs/student/term/ProfitCalculation.java create mode 100644 src/main/java/edu/brown/cs/student/term/profit/ProfitCalculation.java (limited to 'src') diff --git a/src/main/java/edu/brown/cs/student/term/Main.java b/src/main/java/edu/brown/cs/student/term/Main.java index 5ca40e8..3b0a258 100644 --- a/src/main/java/edu/brown/cs/student/term/Main.java +++ b/src/main/java/edu/brown/cs/student/term/Main.java @@ -2,8 +2,7 @@ package edu.brown.cs.student.term; import com.google.common.collect.ImmutableMap; import edu.brown.cs.student.term.hub.Holder; -import edu.brown.cs.student.term.hub.HubSearch; -import edu.brown.cs.student.term.hub.LinkMapper; +import edu.brown.cs.student.term.profit.ProfitCalculation; import edu.brown.cs.student.term.profit.StockHolding; import edu.brown.cs.student.term.hub.SuspicionRanker; import edu.brown.cs.student.term.repl.Command; @@ -27,6 +26,7 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -124,7 +124,7 @@ public final class Main { } /** - * Gets the list of holders with id, name, and suspicion rank + * Gets the list of holders with id, name, and suspicion rank. */ private static class SuspicionRankHandler implements Route { @Override diff --git a/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java b/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java deleted file mode 100644 index 88b4b04..0000000 --- a/src/main/java/edu/brown/cs/student/term/ProfitCalculation.java +++ /dev/null @@ -1,393 +0,0 @@ -package edu.brown.cs.student.term; - - -import edu.brown.cs.student.term.profit.StockHolding; -import org.eclipse.jetty.util.DecoratedObjectFactory; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - - -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.sql.Connection; -import java.sql.Date; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.LinkedList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class ProfitCalculation { - private Connection conn; - private String person; - private Date startTime; - private Date endTime; - private boolean tablesFilled; - - private String BASE_URL = "https://data.alpaca.markets/v1"; - - private String API_KEY = "PKT53Z9QW0TMSSC9XNPQ"; - private String SECRET_KEY = "udvetWCvwyVmZgrjgCPfX3W0nprKBrbunh5wNnCv"; - - //map of stock to list of buy orders, first element in list is oldest - private Map> buyHistoryMap; - - //map of stock to list of buy orders, first element in list is oldest - private Map> sellHistoryMap; - - //map of stock to gains from sell orders - private Map realizedGainsMap; - - //map of stock to gains from increases in value of holdings - private Map unrealizedGainsMap; - - //map to store current prices of stocks -- to avoid repeated api calls - private Map currentStockPrices; - - private double moneyInput; - - /** - * constructor for ProfitCalculation. - * - * @param conn - database connection, with trade data. - * @param person - person of interest to calculate profit for. - * @param startTime - start of period to look at. - * @param endTime - end of period to look at. - */ - public ProfitCalculation(Connection conn, String person, Date startTime, Date endTime) { - this.conn = conn; - this.person = person; - this.startTime = startTime; - this.endTime = endTime; - buyHistoryMap = new HashMap<>(); - sellHistoryMap = new HashMap<>(); - realizedGainsMap = new HashMap<>(); - unrealizedGainsMap = new HashMap<>(); - currentStockPrices = new HashMap<>(); - tablesFilled = false; - } - - /** - * This method fills the maps of sell and buy orders with lists of oldest - new trades. - */ - private void organizeOrders() { - //get a list of trades for a person to consider - try { - PreparedStatement prep; - prep = - conn.prepareStatement("SELECT * FROM \'trades\' WHERE holder_name= ? " - + " AND trade_timestamp BETWEEN ? AND ?" - + "order by trade_timestamp asc;"); - prep.setString(1, this.person); - prep.setDate(2, startTime); - prep.setDate(3, endTime); - ResultSet rs = prep.executeQuery(); - - while (rs.next()) { - String ticker = rs.getString("stock_name"); - int shares = rs.getInt("number_of_shares"); - double price = rs.getDouble("share_price"); - OrderTuple order = new OrderTuple(shares, price, rs.getDate("trade_timestamp")); - - //one element list for first time ticker is seen. - LinkedList oneElement = new LinkedList(); - oneElement.addLast(order); - - //for buy orders, build up buy history - if (rs.getInt("is_buy") != 0) { - moneyInput += shares * price; - if (buyHistoryMap.containsKey(ticker)) { - buyHistoryMap.get(ticker).addLast(order); - } else { - buyHistoryMap.put(ticker, oneElement); - } - } else { - //ignore sell orders for which we do not have buys for - if (buyHistoryMap.containsKey(ticker)) { - if (sellHistoryMap.containsKey(ticker)) { - sellHistoryMap.get(ticker).addLast(order); - } else { - sellHistoryMap.put(ticker, oneElement); - } - } - } - - } - prep.close(); - } catch (SQLException e) { - System.out.println("ERROR: sql error getting trades"); - } - } - - /** - * This method processes the sell orders in the sellHistoryMap to get realized gains. - */ - private void getRealizedGains() { - for (String ticker : sellHistoryMap.keySet()) { - //use FIFO selling - LinkedList sells = sellHistoryMap.get(ticker); - LinkedList buys = buyHistoryMap.get(ticker); - double realizedGain = 0; - - //process each sell order (unless all buy orders are "drained" - for (OrderTuple sell : sells) { - //stop if buys are empty, stop if buy happened after sell - if (buys.isEmpty()) { - break; - } - - int sharesToSell = sell.getShares(); - - //sell off through list of buys - while (sharesToSell > 0 && !buys.isEmpty()) { - //dont sell from buys which didn't exist at the time. - if (sell.getDate().after(buys.getFirst().getDate())) { - OrderTuple buyBundle = buys.removeFirst(); - int sharesAtBundlePrice; - //the buy has more shares than we want to sell - if (buyBundle.getShares() > sharesToSell) { - sharesAtBundlePrice = sharesToSell; - sharesToSell = 0; - //add back the holdings that were not sold - buyBundle.setShares(buyBundle.getShares() - sharesAtBundlePrice); - buys.addFirst(buyBundle); - } else { - sharesToSell -= buyBundle.getShares(); - sharesAtBundlePrice = buyBundle.getShares(); - } - realizedGain += sharesAtBundlePrice * (sell.getCost() - buyBundle.getCost()); - } else { - break; - } - - - } - } - - realizedGainsMap.put(ticker, realizedGain); - } - } - - /** - * get the change in value of stocks which are still held. - */ - private void getUnrealizedGains() { - - //calculate change in value of holdings - for (String ticker : buyHistoryMap.keySet()) { - double unrealizedGains = 0; - - double currentPrice = getCurrentPrice(ticker); - if (currentPrice != -1) { - LinkedList stockHistory = buyHistoryMap.get(ticker); - for (OrderTuple order : stockHistory) { - unrealizedGains += order.getShares() * (currentPrice - order.getCost()); - } - } - unrealizedGainsMap.put(ticker, unrealizedGains); - } - } - - private final class OrderTuple { - private int shares; - private double cost; - private Date date; - - private OrderTuple(int shares, double cost, Date date) { - this.shares = shares; - this.cost = cost; - this.date = date; - } - - public double getCost() { - return cost; - } - - public int getShares() { - return shares; - } - - public Date getDate() { - return date; - } - - public void setShares(int shares) { - this.shares = shares; - } - } - - private double getCurrentPrice(String ticker) { - if (currentStockPrices.containsKey(ticker)) { - return currentStockPrices.get(ticker); - } else { - String PRICE_URL = BASE_URL + "/last/stocks/" + ticker; - - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(PRICE_URL)).setHeader("APCA-API-KEY-ID", API_KEY) - .setHeader("APCA-API-SECRET-KEY", SECRET_KEY) - .build(); - - HttpResponse response = null; - try { - response = client.send(request, - HttpResponse.BodyHandlers.ofString()); - } catch (IOException e) { - e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - - JSONObject object = new JSONObject(response.body()); - try { - double price = object.getJSONObject("last").getDouble("price"); - currentStockPrices.put(ticker, price); - return price; - } catch (JSONException e) { - currentStockPrices.put(ticker, -1.0); - return -1.0; - } - } - - - } - - public double calculateGains() { - if (!tablesFilled) { - organizeOrders(); - getRealizedGains(); - getUnrealizedGains(); - tablesFilled = true; - } - double realizedGains = 0; - double unrealizedGains = 0; - - for (double value : realizedGainsMap.values()) { - realizedGains += value; - } - - for (double value : unrealizedGainsMap.values()) { - unrealizedGains += value; - } - return unrealizedGains + realizedGains; - } - - public List getHoldingsList() { - if (!tablesFilled) { - organizeOrders(); - getRealizedGains(); - getUnrealizedGains(); - tablesFilled = true; - } - - List holdings = new LinkedList<>(); - - for (String key : buyHistoryMap.keySet()) { - double realizedGains = 0; - double unrealizedGains = 0; - if (unrealizedGainsMap.containsKey(key)) { - unrealizedGains = unrealizedGainsMap.get(key); - } - if (realizedGainsMap.containsKey(key)) { - realizedGains = realizedGainsMap.get(key); - } - - int shares = 0; - for (OrderTuple order : buyHistoryMap.get(key)) { - shares += order.getShares(); - } - holdings.add(new StockHolding(key, realizedGains, unrealizedGains, shares)); - } - return holdings; - } - - /** - * return percent change in SPY (SP 500) over the time period. - */ - public double compareToSP500() { - String url = "https://data.alpaca.markets/v1/bars/" - + "day?" - + "symbols=SPY" - + "&start=" + startTime - + "&end=" + endTime; - - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(url)).setHeader("APCA-API-KEY-ID", API_KEY) - .setHeader("APCA-API-SECRET-KEY", SECRET_KEY) - .build(); - - HttpResponse response = null; - try { - response = client.send(request, - HttpResponse.BodyHandlers.ofString()); - } catch (IOException e) { - e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - JSONArray object = new JSONObject(response.body()).getJSONArray("SPY"); - - //get close price of start SPY at start time - double startPrice = object.getJSONObject(0).getDouble("c"); - double endPrice = object.getJSONObject(object.length() - 1).getDouble("c"); - //get percent change - //end - start /start - return ((endPrice - startPrice) / startPrice); - - } - - /** - * get a map for all people in the timeframe of holder_id to percent gain. - * - * @return a map of holder_id to percent gain - */ - public Map getProfitMap() { - Map profitMap = new HashMap<>(); - try { - PreparedStatement prep; - prep = - conn.prepareStatement("SELECT * from trades group by holder_name;"); - ResultSet rs = prep.executeQuery(); - while (rs.next()) { - int id = rs.getInt("holder_id"); - this.person = rs.getString("holder_name"); - //TODO: Temporary fix for the moneyinput divide by 0 error - if(moneyInput == 0){ - moneyInput = 1; - } - profitMap.put(id, this.calculateGains() / moneyInput); - } - } catch (SQLException throwables) { - System.out.println("ERROR: SQl error in profit calculation"); - } - System.out.println(profitMap.toString()); - return profitMap; - } - - public double getMoneyInput() { - return this.moneyInput; - } - - public void setConnection(String filename) throws SQLException, ClassNotFoundException { - - // Initialize the database connection, turn foreign keys on - Class.forName("org.sqlite.JDBC"); - String urlToDB = "jdbc:sqlite:" + filename; - conn = DriverManager.getConnection(urlToDB); - - Statement stat = conn.createStatement(); - stat.executeUpdate("PRAGMA foreign_keys=ON;"); - } - -} diff --git a/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java b/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java index dd959f0..9f5f9c1 100644 --- a/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java +++ b/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java @@ -1,7 +1,7 @@ package edu.brown.cs.student.term.hub; import edu.brown.cs.student.term.DatabaseQuerier; -import edu.brown.cs.student.term.ProfitCalculation; +import edu.brown.cs.student.term.profit.ProfitCalculation; import java.time.Instant; import java.sql.Date; @@ -10,7 +10,8 @@ import java.util.*; public class SuspicionRanker { DatabaseQuerier querier; - public SuspicionRanker(DatabaseQuerier db){ + + public SuspicionRanker(DatabaseQuerier db) { this.querier = db; } @@ -21,7 +22,7 @@ public class SuspicionRanker { return maxEntry.getValue(); } - public List getSuspicionScoreList(Instant start, Instant end){ + public List getSuspicionScoreList(Instant start, Instant end) { List suspicionList = new ArrayList<>(); try { LinkMapper lm = new LinkMapper(querier); @@ -29,8 +30,8 @@ public class SuspicionRanker { Map holderToHubScore = hub.runHubSearch(start, end); ProfitCalculation pc = new ProfitCalculation(DatabaseQuerier.getConn(), "", - new Date(start.toEpochMilli()), - new Date(end.toEpochMilli())); + new Date(start.toEpochMilli()), + new Date(end.toEpochMilli())); Map profitMap = pc.getProfitMap(); @@ -38,7 +39,7 @@ public class SuspicionRanker { double hubMax = getMaxOfMap(holderToHubScore); - for(Holder guy: holderToHubScore.keySet()){ + for (Holder guy : holderToHubScore.keySet()) { double normalizedProfitScore = profitMap.get(guy.getId()) / profitMax; double normalizedHubScore = holderToHubScore.get(guy) / hubMax; double suspicionScore = normalizedHubScore * 0.6 + normalizedProfitScore * 0.4; diff --git a/src/main/java/edu/brown/cs/student/term/profit/ProfitCalculation.java b/src/main/java/edu/brown/cs/student/term/profit/ProfitCalculation.java new file mode 100644 index 0000000..85b2a9a --- /dev/null +++ b/src/main/java/edu/brown/cs/student/term/profit/ProfitCalculation.java @@ -0,0 +1,393 @@ +package edu.brown.cs.student.term.profit; + + +import edu.brown.cs.student.term.profit.StockHolding; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.sql.Connection; +import java.sql.Date; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.LinkedList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ProfitCalculation { + private Connection conn; + private String person; + private Date startTime; + private Date endTime; + private boolean tablesFilled; + + private String BASE_URL = "https://data.alpaca.markets/v1"; + + private String API_KEY = "PKT53Z9QW0TMSSC9XNPQ"; + private String SECRET_KEY = "udvetWCvwyVmZgrjgCPfX3W0nprKBrbunh5wNnCv"; + + //map of stock to list of buy orders, first element in list is oldest + private Map> buyHistoryMap; + + //map of stock to list of buy orders, first element in list is oldest + private Map> sellHistoryMap; + + //map of stock to gains from sell orders + private Map realizedGainsMap; + + //map of stock to gains from increases in value of holdings + private Map unrealizedGainsMap; + + //map to store current prices of stocks -- to avoid repeated api calls + private Map currentStockPrices; + + private double moneyInput; + + /** + * constructor for ProfitCalculation. + * + * @param conn - database connection, with trade data. + * @param person - person of interest to calculate profit for. + * @param startTime - start of period to look at. + * @param endTime - end of period to look at. + */ + public ProfitCalculation(Connection conn, String person, Date startTime, Date endTime) { + this.conn = conn; + this.person = person; + this.startTime = startTime; + this.endTime = endTime; + buyHistoryMap = new HashMap<>(); + sellHistoryMap = new HashMap<>(); + realizedGainsMap = new HashMap<>(); + unrealizedGainsMap = new HashMap<>(); + currentStockPrices = new HashMap<>(); + tablesFilled = false; + } + + /** + * This method fills the maps of sell and buy orders with lists of oldest - new trades. + */ + private void organizeOrders() { + //get a list of trades for a person to consider + try { + PreparedStatement prep; + prep = + conn.prepareStatement("SELECT * FROM \'trades\' WHERE holder_name= ? " + + " AND trade_timestamp BETWEEN ? AND ?" + + "order by trade_timestamp asc;"); + prep.setString(1, this.person); + prep.setDate(2, startTime); + prep.setDate(3, endTime); + ResultSet rs = prep.executeQuery(); + + while (rs.next()) { + String ticker = rs.getString("stock_name"); + int shares = rs.getInt("number_of_shares"); + double price = rs.getDouble("share_price"); + OrderTuple order = new OrderTuple(shares, price, rs.getDate("trade_timestamp")); + + //one element list for first time ticker is seen. + LinkedList oneElement = new LinkedList(); + oneElement.addLast(order); + + //for buy orders, build up buy history + if (rs.getInt("is_buy") != 0) { + moneyInput += shares * price; + if (buyHistoryMap.containsKey(ticker)) { + buyHistoryMap.get(ticker).addLast(order); + } else { + buyHistoryMap.put(ticker, oneElement); + } + } else { + //ignore sell orders for which we do not have buys for + if (buyHistoryMap.containsKey(ticker)) { + if (sellHistoryMap.containsKey(ticker)) { + sellHistoryMap.get(ticker).addLast(order); + } else { + sellHistoryMap.put(ticker, oneElement); + } + } + } + + } + prep.close(); + } catch (SQLException e) { + System.out.println("ERROR: sql error getting trades"); + } + } + + /** + * This method processes the sell orders in the sellHistoryMap to get realized gains. + */ + private void getRealizedGains() { + for (String ticker : sellHistoryMap.keySet()) { + //use FIFO selling + LinkedList sells = sellHistoryMap.get(ticker); + LinkedList buys = buyHistoryMap.get(ticker); + double realizedGain = 0; + + //process each sell order (unless all buy orders are "drained" + for (OrderTuple sell : sells) { + //stop if buys are empty, stop if buy happened after sell + if (buys.isEmpty()) { + break; + } + + int sharesToSell = sell.getShares(); + + //sell off through list of buys + while (sharesToSell > 0 && !buys.isEmpty()) { + //dont sell from buys which didn't exist at the time. + if (sell.getDate().after(buys.getFirst().getDate())) { + OrderTuple buyBundle = buys.removeFirst(); + int sharesAtBundlePrice; + //the buy has more shares than we want to sell + if (buyBundle.getShares() > sharesToSell) { + sharesAtBundlePrice = sharesToSell; + sharesToSell = 0; + //add back the holdings that were not sold + buyBundle.setShares(buyBundle.getShares() - sharesAtBundlePrice); + buys.addFirst(buyBundle); + } else { + sharesToSell -= buyBundle.getShares(); + sharesAtBundlePrice = buyBundle.getShares(); + } + realizedGain += sharesAtBundlePrice * (sell.getCost() - buyBundle.getCost()); + } else { + break; + } + + + } + } + + realizedGainsMap.put(ticker, realizedGain); + } + } + + /** + * get the change in value of stocks which are still held. + */ + private void getUnrealizedGains() { + + //calculate change in value of holdings + for (String ticker : buyHistoryMap.keySet()) { + double unrealizedGains = 0; + + double currentPrice = getCurrentPrice(ticker); + if (currentPrice != -1) { + LinkedList stockHistory = buyHistoryMap.get(ticker); + for (OrderTuple order : stockHistory) { + unrealizedGains += order.getShares() * (currentPrice - order.getCost()); + } + } + unrealizedGainsMap.put(ticker, unrealizedGains); + } + } + + private final class OrderTuple { + private int shares; + private double cost; + private Date date; + + private OrderTuple(int shares, double cost, Date date) { + this.shares = shares; + this.cost = cost; + this.date = date; + } + + public double getCost() { + return cost; + } + + public int getShares() { + return shares; + } + + public Date getDate() { + return date; + } + + public void setShares(int shares) { + this.shares = shares; + } + } + + private double getCurrentPrice(String ticker) { + if (currentStockPrices.containsKey(ticker)) { + return currentStockPrices.get(ticker); + } else { + String PRICE_URL = BASE_URL + "/last/stocks/" + ticker; + + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(PRICE_URL)).setHeader("APCA-API-KEY-ID", API_KEY) + .setHeader("APCA-API-SECRET-KEY", SECRET_KEY) + .build(); + + HttpResponse response = null; + try { + response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + + JSONObject object = new JSONObject(response.body()); + try { + double price = object.getJSONObject("last").getDouble("price"); + currentStockPrices.put(ticker, price); + return price; + } catch (JSONException e) { + currentStockPrices.put(ticker, -1.0); + return -1.0; + } + } + + + } + + public double calculateGains() { + if (!tablesFilled) { + organizeOrders(); + getRealizedGains(); + getUnrealizedGains(); + tablesFilled = true; + } + double realizedGains = 0; + double unrealizedGains = 0; + + for (double value : realizedGainsMap.values()) { + realizedGains += value; + } + + for (double value : unrealizedGainsMap.values()) { + unrealizedGains += value; + } + return unrealizedGains + realizedGains; + } + + public List getHoldingsList() { + if (!tablesFilled) { + organizeOrders(); + getRealizedGains(); + getUnrealizedGains(); + tablesFilled = true; + } + + List holdings = new LinkedList<>(); + + for (String key : buyHistoryMap.keySet()) { + double realizedGains = 0; + double unrealizedGains = 0; + if (unrealizedGainsMap.containsKey(key)) { + unrealizedGains = unrealizedGainsMap.get(key); + } + if (realizedGainsMap.containsKey(key)) { + realizedGains = realizedGainsMap.get(key); + } + + int shares = 0; + for (OrderTuple order : buyHistoryMap.get(key)) { + shares += order.getShares(); + } + holdings.add(new StockHolding(key, realizedGains, unrealizedGains, shares)); + } + return holdings; + } + + /** + * return percent change in SPY (SP 500) over the time period. + */ + public double compareToSP500() { + String url = "https://data.alpaca.markets/v1/bars/" + + "day?" + + "symbols=SPY" + + "&start=" + startTime + + "&end=" + endTime; + + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)).setHeader("APCA-API-KEY-ID", API_KEY) + .setHeader("APCA-API-SECRET-KEY", SECRET_KEY) + .build(); + + HttpResponse response = null; + try { + response = client.send(request, + HttpResponse.BodyHandlers.ofString()); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + JSONArray object = new JSONObject(response.body()).getJSONArray("SPY"); + + //get close price of start SPY at start time + double startPrice = object.getJSONObject(0).getDouble("c"); + double endPrice = object.getJSONObject(object.length() - 1).getDouble("c"); + //get percent change + //end - start /start + return ((endPrice - startPrice) / startPrice); + + } + + /** + * get a map for all people in the timeframe of holder_id to percent gain. + * + * @return a map of holder_id to percent gain + */ + public Map getProfitMap() { + Map profitMap = new HashMap<>(); + try { + PreparedStatement prep; + prep = + conn.prepareStatement("SELECT * from trades group by holder_name;"); + ResultSet rs = prep.executeQuery(); + while (rs.next()) { + int id = rs.getInt("holder_id"); + this.person = rs.getString("holder_name"); + if (moneyInput == 0) { + profitMap.put(id, 0.0); + } else { + profitMap.put(id, this.calculateGains() / moneyInput); + } + + } + } catch (SQLException throwables) { + System.out.println("ERROR: SQl error in profit calculation"); + } + System.out.println(profitMap.toString()); + return profitMap; + } + + public double getMoneyInput() { + return this.moneyInput; + } + + public void setConnection(String filename) throws SQLException, ClassNotFoundException { + + // Initialize the database connection, turn foreign keys on + Class.forName("org.sqlite.JDBC"); + String urlToDB = "jdbc:sqlite:" + filename; + conn = DriverManager.getConnection(urlToDB); + + Statement stat = conn.createStatement(); + stat.executeUpdate("PRAGMA foreign_keys=ON;"); + } + +} -- cgit v1.2.3-70-g09d2