├── qmoney ├── src │ ├── test │ │ ├── resources │ │ │ └── assessments │ │ │ │ ├── empty.json │ │ │ │ ├── trades.json │ │ │ │ ├── trades_old.json │ │ │ │ ├── trades_invalid_dates.json │ │ │ │ └── trades_invalid_stock.json │ │ └── java │ │ │ └── com │ │ │ └── crio │ │ │ └── warmup │ │ │ └── stock │ │ │ ├── ModuleOneTest.java │ │ │ ├── ModuleThreeTest.java │ │ │ ├── portfolio │ │ │ ├── PortfolioManagerPerformanceTest.java │ │ │ ├── PortfolioManagerFactoryTest.java │ │ │ └── PortfolioManagerTest.java │ │ │ ├── ModuleTwoTest.java │ │ │ └── PortfolioManagerApplicationTest.java │ └── main │ │ ├── resources │ │ ├── application.properties │ │ ├── trades.json │ │ └── log4j2.xml │ │ └── java │ │ └── com │ │ └── crio │ │ └── warmup │ │ └── stock │ │ ├── dto │ │ ├── Candle.java │ │ ├── TotalReturnsDto.java │ │ ├── AnnualizedReturn.java │ │ ├── PortfolioTrade.java │ │ └── TiingoCandle.java │ │ ├── portfolio │ │ ├── PortfolioManagerFactory.java │ │ ├── PortfolioManager.java │ │ └── PortfolioManagerImpl.java │ │ ├── log │ │ └── UncaughtExceptionHandler.java │ │ └── PortfolioManagerApplication.java ├── settings.gradle ├── build.gradle └── gradlew ├── settings.gradle ├── __CRIO__ ├── metadata.json ├── pre-push ├── ruleset.xml ├── checkstyle.xml └── CrioEditorStyle.xml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── README.md ├── .gitattributes └── gradlew /qmoney/src/test/resources/assessments/empty.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /qmoney/src/test/java/com/crio/warmup/stock/ModuleOneTest.java: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /qmoney/src/test/java/com/crio/warmup/stock/ModuleThreeTest.java: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /qmoney/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'JAVA_ME_MODULE_FILE' 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'qmoney' 2 | rootProject.name = 'ME_QMONEY_MODULE_GRADLE' 3 | -------------------------------------------------------------------------------- /qmoney/src/test/java/com/crio/warmup/stock/portfolio/PortfolioManagerPerformanceTest.java: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /__CRIO__/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "module_id": "ME_QMONEY_MODULE_GRADLE", 3 | "me_id": "ME_QMONEY" 4 | } 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vanshikagarg17/Crio_QMoney/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /qmoney/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | logging.level.org.springframework.web=ERROR 3 | logging.level.controllers.Application=ERROR 4 | logging.file=qmoney_logfile.log 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /qmoney/src/main/java/com/crio/warmup/stock/dto/Candle.java: -------------------------------------------------------------------------------- 1 | 2 | package com.crio.warmup.stock.dto; 3 | 4 | import java.time.LocalDate; 5 | 6 | public interface Candle { 7 | 8 | Double getOpen(); 9 | 10 | Double getClose(); 11 | 12 | Double getHigh(); 13 | 14 | Double getLow(); 15 | 16 | LocalDate getDate(); 17 | } 18 | -------------------------------------------------------------------------------- /__CRIO__/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # stash any unstaged changes 4 | git stash -q --keep-index 5 | 6 | # run the tests with the gradle wrapper 7 | ./gradlew build -x test 8 | 9 | # store the last exit code in a variable 10 | RESULT=$? 11 | 12 | # unstash the unstashed changes 13 | git stash pop -q 14 | 15 | # return the './gradlew test' exit code 16 | exit $RESULT 17 | -------------------------------------------------------------------------------- /qmoney/src/main/resources/trades.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "symbol": "AAPL", 4 | "quantity": 100, 5 | "tradeType": "BUY", 6 | "purchaseDate": "2019-01-02" 7 | }, 8 | { 9 | "symbol": "MSFT", 10 | "quantity": 10, 11 | "tradeType": "BUY", 12 | "purchaseDate": "2019-01-02" 13 | }, 14 | { 15 | "symbol": "GOOGL", 16 | "quantity": 50, 17 | "tradeType": "BUY", 18 | "purchaseDate": "2019-01-02" 19 | } 20 | ] -------------------------------------------------------------------------------- /qmoney/src/test/resources/assessments/trades.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "symbol": "MSFT", 4 | "quantity": 100, 5 | "tradeType": "BUY", 6 | "purchaseDate": "2019-01-07" 7 | }, 8 | { 9 | "symbol": "CSCO", 10 | "quantity": 10, 11 | "tradeType": "BUY", 12 | "purchaseDate": "2019-01-09" 13 | }, 14 | { 15 | "symbol": "CTS", 16 | "quantity": 50, 17 | "tradeType": "BUY", 18 | "purchaseDate": "2019-01-29" 19 | } 20 | ] -------------------------------------------------------------------------------- /qmoney/src/test/resources/assessments/trades_old.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "symbol": "CTS", 4 | "quantity": 100, 5 | "tradeType": "BUY", 6 | "purchaseDate": "2017-01-05" 7 | }, 8 | { 9 | "symbol": "MMM", 10 | "quantity": 10, 11 | "tradeType": "BUY", 12 | "purchaseDate": "2016-01-11" 13 | }, 14 | { 15 | "symbol": "ABBV", 16 | "quantity": 50, 17 | "tradeType": "BUY", 18 | "purchaseDate": "2016-01-29" 19 | } 20 | ] -------------------------------------------------------------------------------- /qmoney/src/test/resources/assessments/trades_invalid_dates.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "symbol": "MSFT", 4 | "quantity": 100, 5 | "tradeType": "BUY", 6 | "purchaseDate": "2017-01-05" 7 | }, 8 | { 9 | "symbol": "CSCO", 10 | "quantity": 10, 11 | "tradeType": "BUY", 12 | "purchaseDate": "2017-01-09" 13 | }, 14 | { 15 | "symbol": "CTS", 16 | "quantity": 50, 17 | "tradeType": "BUY", 18 | "purchaseDate": "2018-01-29" 19 | } 20 | ] -------------------------------------------------------------------------------- /qmoney/src/test/resources/assessments/trades_invalid_stock.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "symbol": "CISCO", 4 | "quantity": 100, 5 | "tradeType": "BUY", 6 | "purchaseDate": "2017-01-05" 7 | }, 8 | { 9 | "symbol": "CSCO", 10 | "quantity": 10, 11 | "tradeType": "BUY", 12 | "purchaseDate": "2017-01-09" 13 | }, 14 | { 15 | "symbol": "CTS", 16 | "quantity": 50, 17 | "tradeType": "BUY", 18 | "purchaseDate": "2018-01-29" 19 | } 20 | ] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | /build/ 4 | */build/ 5 | /bin/ 6 | */bin/ 7 | !gradle/wrapper/gradle-wrapper.jar 8 | *.log 9 | *.gz 10 | *.class 11 | 12 | ### STS ### 13 | .apt_generated 14 | .classpath 15 | .factorypath 16 | .project 17 | .settings 18 | .springBeans 19 | .sts4-cache 20 | 21 | ### IntelliJ IDEA ### 22 | .idea 23 | *.iws 24 | *.iml 25 | *.ipr 26 | /out/ 27 | */out/ 28 | out/ 29 | 30 | 31 | ### NetBeans ### 32 | /nbproject/private/ 33 | /nbbuild/ 34 | /dist/ 35 | /nbdist/ 36 | /.nb-gradle/ 37 | 38 | ### VS Code ### 39 | .vscode/ 40 | -------------------------------------------------------------------------------- /qmoney/src/main/java/com/crio/warmup/stock/portfolio/PortfolioManagerFactory.java: -------------------------------------------------------------------------------- 1 | 2 | package com.crio.warmup.stock.portfolio; 3 | 4 | import org.springframework.web.client.RestTemplate; 5 | 6 | public class PortfolioManagerFactory { 7 | 8 | // TODO: CRIO_TASK_MODULE_REFACTOR 9 | // Implement the method in such a way that it will return new Instance of 10 | // PortfolioManager using RestTemplate provided. 11 | public static PortfolioManager getPortfolioManager(RestTemplate restTemplate) { 12 | 13 | return new PortfolioManagerImpl(restTemplate); 14 | } 15 | 16 | 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /qmoney/src/main/java/com/crio/warmup/stock/portfolio/PortfolioManager.java: -------------------------------------------------------------------------------- 1 | 2 | package com.crio.warmup.stock.portfolio; 3 | 4 | import com.crio.warmup.stock.dto.AnnualizedReturn; 5 | import com.crio.warmup.stock.dto.PortfolioTrade; 6 | import com.fasterxml.jackson.core.JsonProcessingException; 7 | 8 | import java.time.LocalDate; 9 | import java.util.List; 10 | 11 | public interface PortfolioManager { 12 | 13 | 14 | //CHECKSTYLE:OFF 15 | List calculateAnnualizedReturn(List portfolioTrades, 16 | LocalDate endDate) throws JsonProcessingException 17 | ; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /__CRIO__/ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | Exclude certain rules 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /qmoney/src/main/java/com/crio/warmup/stock/dto/TotalReturnsDto.java: -------------------------------------------------------------------------------- 1 | 2 | package com.crio.warmup.stock.dto; 3 | 4 | public class TotalReturnsDto { 5 | 6 | private String symbol; 7 | private Double closingPrice; 8 | 9 | public TotalReturnsDto(String symbol, Double closingPrice) { 10 | this.symbol = symbol; 11 | this.closingPrice = closingPrice; 12 | } 13 | 14 | public String getSymbol() { 15 | return symbol; 16 | } 17 | 18 | public void setSymbol(String symbol) { 19 | this.symbol = symbol; 20 | } 21 | 22 | public Double getClosingPrice() { 23 | return closingPrice; 24 | } 25 | 26 | public void setClosingPrice(Double closingPrice) { 27 | this.closingPrice = closingPrice; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /qmoney/src/main/java/com/crio/warmup/stock/dto/AnnualizedReturn.java: -------------------------------------------------------------------------------- 1 | 2 | package com.crio.warmup.stock.dto; 3 | 4 | public class AnnualizedReturn { 5 | 6 | private final String symbol; 7 | private final Double annualizedReturn; 8 | private final Double totalReturns; 9 | 10 | public AnnualizedReturn(String symbol, Double annualizedReturn, Double totalReturns) { 11 | this.symbol = symbol; 12 | this.annualizedReturn = annualizedReturn; 13 | this.totalReturns = totalReturns; 14 | } 15 | 16 | public String getSymbol() { 17 | return symbol; 18 | } 19 | 20 | public Double getAnnualizedReturn() { 21 | return annualizedReturn; 22 | } 23 | 24 | public Double getTotalReturns() { 25 | return totalReturns; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /qmoney/src/test/java/com/crio/warmup/stock/portfolio/PortfolioManagerFactoryTest.java: -------------------------------------------------------------------------------- 1 | 2 | package com.crio.warmup.stock.portfolio; 3 | 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | import org.mockito.Mock; 8 | import org.mockito.junit.jupiter.MockitoExtension; 9 | import org.springframework.web.client.RestTemplate; 10 | 11 | @ExtendWith(MockitoExtension.class) 12 | class PortfolioManagerFactoryTest { 13 | 14 | @Mock 15 | private RestTemplate restTemplate; 16 | 17 | 18 | 19 | @Test 20 | void getPortfolioManager() { 21 | Assertions.assertTrue(PortfolioManagerFactory.getPortfolioManager(restTemplate) 22 | instanceof PortfolioManager); 23 | } 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /qmoney/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /qmoney/src/main/java/com/crio/warmup/stock/log/UncaughtExceptionHandler.java: -------------------------------------------------------------------------------- 1 | 2 | package com.crio.warmup.stock.log; 3 | 4 | import com.fasterxml.jackson.databind.node.ArrayNode; 5 | import com.fasterxml.jackson.databind.node.JsonNodeFactory; 6 | import com.fasterxml.jackson.databind.node.ObjectNode; 7 | import org.apache.logging.log4j.LogManager; 8 | import org.apache.logging.log4j.Logger; 9 | 10 | public class UncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { 11 | 12 | private static final Logger log = LogManager.getLogger(UncaughtExceptionHandler.class); 13 | 14 | @Override 15 | public void uncaughtException(Thread t, Throwable e) { 16 | ObjectNode logEventJsonObjNode = JsonNodeFactory.instance.objectNode(); 17 | 18 | if (e.getStackTrace() != null && e.getStackTrace().length > 0) { 19 | ArrayNode logStacktraceJsonArrNode = JsonNodeFactory.instance.arrayNode(); 20 | 21 | for (StackTraceElement stackTraceElement : e.getStackTrace()) { 22 | logStacktraceJsonArrNode.add(stackTraceElement.toString()); 23 | } 24 | logEventJsonObjNode.set("stacktrace", logStacktraceJsonArrNode); 25 | } 26 | 27 | logEventJsonObjNode.put("cause", e.toString()); 28 | 29 | log.error(logEventJsonObjNode.toString(), e); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crio_QMoney 2 | QMoney is a visual stock portfolio analyzer. It helps portfolio managers make trade recommendations for their clients. 3 | 4 | - Implemented the core logic of the portfolio manager and published it as a library. 5 | 6 | - Refactored code to add support for multiple stock quote services. 7 | 8 | - Improved application stability and performance. 9 | 10 | 11 | Please feel free to fork it and raise issues if you think that something could be done better. Happy coding!! 12 | 13 | You can connect with me here: 14 | 15 | [![Github Badge](https://img.shields.io/badge/Follow-blue?style=social&logo=Github&link=https://github.com/Vanshikagarg17/?igshid=k8l41dsudxvo)](https://github.com/Vanshikagarg17/?igshid=k8l41dsudxvo) 16 | [![Twitter Badge](http://img.shields.io/badge/-@vanshika_garg17-1ca0f1?style=social&logo=twitter&logoColor=blue&link=https://twitter.com/vanshika_garg17)](https://twitter.com/vanshika_garg17) 17 | [![Linkedin Badge](https://img.shields.io/badge/-Vanshika%20Garg-blue?style=social&logo=Linkedin&logoColor=blue&link=https://www.linkedin.com/in/vanshika-garg-9297a3188/)](https://www.linkedin.com/in/vanshika-garg-9297a3188/) 18 | [![Instagram Badge](https://img.shields.io/badge/vanshikaaaaa_-blue?style=social&logo=Instagram&link=https://instagram.com/vanshikaaaaa_?igshid=k8l41dsudxvo)](https://instagram.com/vanshikaaaaa_?igshid=k8l41dsudxvo) 19 | -------------------------------------------------------------------------------- /qmoney/src/main/java/com/crio/warmup/stock/dto/PortfolioTrade.java: -------------------------------------------------------------------------------- 1 | 2 | package com.crio.warmup.stock.dto; 3 | 4 | import java.time.LocalDate; 5 | 6 | public class PortfolioTrade { 7 | 8 | public PortfolioTrade() { } 9 | 10 | public static enum TradeType { 11 | BUY, 12 | SELL 13 | } 14 | 15 | private String symbol; 16 | private int quantity; 17 | private TradeType tradeType; 18 | private LocalDate purchaseDate; 19 | 20 | public PortfolioTrade(String symbol, int quantity, LocalDate purchaseDate) { 21 | this.symbol = symbol; 22 | this.quantity = quantity; 23 | this.purchaseDate = purchaseDate; 24 | this.tradeType = TradeType.BUY; 25 | } 26 | 27 | public void setSymbol(String symbol) { 28 | this.symbol = symbol; 29 | } 30 | 31 | public void setQuantity(int quantity) { 32 | this.quantity = quantity; 33 | } 34 | 35 | public void setTradeType(TradeType tradeType) { 36 | this.tradeType = tradeType; 37 | } 38 | 39 | public void setPurchaseDate(LocalDate purchaseDate) { 40 | this.purchaseDate = purchaseDate; 41 | } 42 | 43 | //solution 44 | public String getSymbol() { 45 | return symbol; 46 | } 47 | 48 | public int getQuantity() { 49 | return quantity; 50 | } 51 | 52 | public LocalDate getPurchaseDate() { 53 | return purchaseDate; 54 | } 55 | 56 | public TradeType getTradeType() { 57 | return tradeType; 58 | } 59 | //solution 60 | 61 | } 62 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | __CRIO__/metadata.json merge=copy-merge 2 | settings.gradle merge=copy-merge 3 | build.gradle merge=copy-merge 4 | qmoney/src/test/java/com/crio/warmup/stock/portfolio/PortfolioManagerTest.java merge=copy-merge 5 | qmoney/src/test/java/com/crio/warmup/stock/portfolio/PortfolioManagerFactoryTest.java merge=copy-merge 6 | qmoney/src/test/java/com/crio/warmup/stock/portfolio/PortfolioManagerPerformanceTest.java merge=copy-merge 7 | qmoney/src/test/java/com/crio/warmup/stock/ModuleOneTest.java merge=copy-merge 8 | qmoney/src/test/java/com/crio/warmup/stock/PortfolioManagerApplicationTest.java merge=copy-merge 9 | qmoney/src/test/java/com/crio/warmup/stock/ModuleThreeTest.java merge=copy-merge 10 | qmoney/src/test/java/com/crio/warmup/stock/quotes/StockQuoteServiceFactoryTest.java merge=copy-merge 11 | qmoney/src/test/java/com/crio/warmup/stock/quotes/AlphavantageLimitTest.java merge=copy-merge 12 | qmoney/src/test/java/com/crio/warmup/stock/quotes/AlphavantageServiceTest.java merge=copy-merge 13 | qmoney/src/test/java/com/crio/warmup/stock/quotes/TiingoServiceTest.java merge=copy-merge 14 | qmoney/src/test/java/com/crio/warmup/stock/ModuleTwoTest.java merge=copy-merge 15 | qmoney/src/main/java/com/crio/warmup/stock/quotes/StockQuotesService.java merge=copy-merge 16 | qmoney/src/main/java/com/crio/warmup/stock/portfolio/PortfolioManager.java merge=copy-merge 17 | qmoney/src/main/java/com/crio/warmup/stock/dto/Candle.java merge=copy-merge 18 | qmoney/src/main/java/com/crio/warmup/stock/dto/TiingoCandle.java merge=copy-merge 19 | -------------------------------------------------------------------------------- /qmoney/src/main/java/com/crio/warmup/stock/dto/TiingoCandle.java: -------------------------------------------------------------------------------- 1 | 2 | package com.crio.warmup.stock.dto; 3 | 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import java.time.LocalDate; 6 | 7 | @JsonIgnoreProperties(ignoreUnknown = true) 8 | public class TiingoCandle implements Candle { 9 | 10 | private Double open; 11 | private Double close; 12 | private Double high; 13 | private Double low; 14 | private LocalDate date; 15 | 16 | @Override 17 | public Double getOpen() { 18 | return open; 19 | } 20 | 21 | public void setOpen(Double open) { 22 | this.open = open; 23 | } 24 | 25 | @Override 26 | public Double getClose() { 27 | return close; 28 | } 29 | 30 | public void setClose(Double close) { 31 | this.close = close; 32 | } 33 | 34 | @Override 35 | public Double getHigh() { 36 | return high; 37 | } 38 | 39 | public void setHigh(Double high) { 40 | this.high = high; 41 | } 42 | 43 | @Override 44 | public Double getLow() { 45 | return low; 46 | } 47 | 48 | public void setLow(Double low) { 49 | this.low = low; 50 | } 51 | 52 | @Override 53 | public LocalDate getDate() { 54 | return date; 55 | } 56 | 57 | public void setDate(LocalDate timeStamp) { 58 | this.date = timeStamp; 59 | } 60 | 61 | @Override 62 | public String toString() { 63 | return "TiingoCandle{" 64 | + "open=" + open 65 | + ", close=" + close 66 | + ", high=" + high 67 | + ", low=" + low 68 | + ", date=" + date 69 | + '}'; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /qmoney/src/test/java/com/crio/warmup/stock/ModuleTwoTest.java: -------------------------------------------------------------------------------- 1 | 2 | package com.crio.warmup.stock; 3 | 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import org.junit.jupiter.api.Assertions; 7 | import org.junit.jupiter.api.Test; 8 | 9 | class ModuleTwoTest { 10 | 11 | @Test 12 | void mainReadQuotes() throws Exception { 13 | //given 14 | String filename = "assessments/trades.json"; 15 | List expected = Arrays.asList(new String[]{"CTS", "CSCO", "MSFT"}); 16 | 17 | //when 18 | List actual = PortfolioManagerApplication 19 | .mainReadQuotes(new String[]{filename, "2019-12-12"}); 20 | 21 | //then 22 | Assertions.assertEquals(expected, actual); 23 | } 24 | 25 | @Test 26 | void mainReadQuotesEdgeCase() throws Exception { 27 | //given 28 | String filename = "assessments/empty.json"; 29 | List expected = Arrays.asList(new String[]{}); 30 | 31 | //when 32 | List actual = PortfolioManagerApplication 33 | .mainReadQuotes(new String[]{filename, "2019-12-12"}); 34 | 35 | //then 36 | Assertions.assertEquals(expected, actual); 37 | } 38 | 39 | @Test 40 | void mainReadQuotesInvalidDates() throws Exception { 41 | //given 42 | String filename = "assessments/trades_invalid_dates.json"; 43 | //when 44 | Assertions.assertThrows(RuntimeException.class, () -> PortfolioManagerApplication 45 | .mainReadQuotes(new String[]{filename, "2017-12-12"})); 46 | 47 | } 48 | 49 | 50 | @Test 51 | void mainReadQuotesInvalidStocks() throws Exception { 52 | //given 53 | String filename = "assessments/trades_invalid_stock.json"; 54 | //when 55 | Assertions.assertThrows(RuntimeException.class, () -> PortfolioManagerApplication 56 | .mainReadQuotes(new String[]{filename, "2017-12-12"})); 57 | 58 | } 59 | 60 | @Test 61 | void mainReadQuotesOldTrades() throws Exception { 62 | //given 63 | String filename = "assessments/trades_old.json"; 64 | List expected = Arrays.asList(new String[]{"CTS", "ABBV", "MMM"}); 65 | 66 | //when 67 | List actual = PortfolioManagerApplication 68 | .mainReadQuotes(new String[]{filename, "2019-12-12"}); 69 | 70 | //then 71 | Assertions.assertEquals(expected, actual); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /qmoney/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'io.spring.dependency-management' 3 | id 'java' 4 | } 5 | 6 | group = 'com.crio.warmup' 7 | version = '0.0.1-SNAPSHOT' 8 | sourceCompatibility = '1.8' 9 | 10 | configurations { 11 | compileOnly { 12 | extendsFrom annotationProcessor 13 | } 14 | } 15 | 16 | 17 | apply plugin: 'application' 18 | mainClassName = 'com.crio.warmup.stock.PortfolioManagerApplication' 19 | 20 | 21 | repositories { 22 | mavenLocal() 23 | mavenCentral() 24 | } 25 | 26 | dependencies { 27 | compile group: 'org.decampo', name: 'xirr', version: '0.1' 28 | compile group: 'org.springframework', name: 'spring-web', version: '5.2.2.RELEASE' 29 | compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.10.1' 30 | compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.10.1' 31 | 32 | compile("org.springframework.boot:spring-boot-starter-log4j2:$rootProject.ext.springBootVersion") { 33 | exclude group: 'org.apache.logging.log4j', module: 'log4j-slf4j-impl' 34 | exclude group: 'org.apache.logging.log4j', module: 'log4j-core' 35 | exclude group: 'org.apache.logging.log4j', module: 'log4j-jul' 36 | exclude group: 'org.slf4j', module: 'jul-to-slf4j' 37 | } 38 | 39 | // log4j2 dependencies 40 | compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.12.1' 41 | compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.12.1' 42 | //compile group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.12.1' 43 | compile group: 'org.apache.logging.log4j', name: 'log4j-jul', version: '2.12.1' 44 | compile group: 'org.slf4j', name: 'jul-to-slf4j', version: '1.7.28' 45 | 46 | 47 | //other libraries apart from spring 48 | compile group: 'org.apache.commons', name: 'commons-math3', version: '3.6.1' 49 | 50 | //junits 51 | testCompile group: 'org.mockito', name: 'mockito-junit-jupiter', version: '3.2.4' 52 | testCompile 'org.junit.jupiter:junit-jupiter-api:5.5.2' 53 | testImplementation('org.junit.jupiter:junit-jupiter:5.5.2') 54 | } 55 | 56 | test { 57 | useJUnitPlatform() 58 | } 59 | 60 | 61 | publishing { 62 | publications { 63 | 64 | maven(MavenPublication) { 65 | groupId 'com.crio.warmup' 66 | artifactId 'annual-return' 67 | version '1.0.0' 68 | 69 | from components.java 70 | } 71 | } 72 | 73 | } 74 | 75 | -------------------------------------------------------------------------------- /qmoney/src/test/java/com/crio/warmup/stock/PortfolioManagerApplicationTest.java: -------------------------------------------------------------------------------- 1 | 2 | package com.crio.warmup.stock; 3 | 4 | import com.crio.warmup.stock.dto.AnnualizedReturn; 5 | import com.crio.warmup.stock.dto.PortfolioTrade; 6 | import java.time.LocalDate; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | import org.junit.jupiter.api.Assertions; 11 | import org.junit.jupiter.api.Test; 12 | 13 | class PortfolioManagerApplicationTest { 14 | 15 | @Test 16 | void mainReadFile() throws Exception { 17 | //given 18 | String filename = "trades.json"; 19 | List expected = Arrays.asList(new String[]{"AAPL", "MSFT", "GOOGL"}); 20 | 21 | //when 22 | List results = PortfolioManagerApplication 23 | .mainReadFile(new String[]{filename}); 24 | 25 | //then 26 | Assertions.assertEquals(expected, results); 27 | } 28 | 29 | 30 | @Test 31 | void mainReadQuotes() throws Exception { 32 | //given 33 | String filename = "trades.json"; 34 | List expected = Arrays.asList(new String[]{"MSFT", "AAPL", "GOOGL"}); 35 | 36 | //when 37 | List actual = PortfolioManagerApplication 38 | .mainReadQuotes(new String[]{filename, "2019-12-12"}); 39 | 40 | //then 41 | Assertions.assertEquals(expected, actual); 42 | } 43 | 44 | 45 | @Test 46 | void mainCalculateAnnualReturn() throws Exception { 47 | //given 48 | String filename = "trades.json"; 49 | //when 50 | List result = PortfolioManagerApplication 51 | .mainCalculateSingleReturn(new String[]{filename, "2019-12-12"}); 52 | 53 | //then 54 | List symbols = result.stream().map(AnnualizedReturn::getSymbol) 55 | .collect(Collectors.toList()); 56 | Assertions.assertEquals(0.814, result.get(0).getAnnualizedReturn(), 0.01); 57 | Assertions.assertEquals(0.584, result.get(1).getAnnualizedReturn(), 0.01); 58 | Assertions.assertEquals(0.33, result.get(2).getAnnualizedReturn(),0.01); 59 | Assertions.assertEquals(Arrays.asList(new String[]{"AAPL", "MSFT", "GOOGL"}), symbols); 60 | 61 | } 62 | 63 | @Test 64 | public void testCalculateAnnualizedReturn() { 65 | PortfolioTrade trade = new PortfolioTrade("AAPL", 50, LocalDate.parse("2015-01-01")); 66 | AnnualizedReturn returns = PortfolioManagerApplication 67 | .calculateAnnualizedReturns(LocalDate.parse("2018-01-01"), 68 | trade, 10000.00, 11000.00); 69 | Assertions.assertEquals(returns.getAnnualizedReturn(), 0.0322, 0.0001); 70 | } 71 | 72 | @Test 73 | public void testCalculateAnnualizedReturnGoogl() { 74 | PortfolioTrade trade = new PortfolioTrade("GOOGL", 50, LocalDate.parse("2019-01-02")); 75 | AnnualizedReturn returns = PortfolioManagerApplication 76 | .calculateAnnualizedReturns(LocalDate.parse("2019-12-12"), 77 | trade, 1054.00, 1348.00); 78 | Assertions.assertEquals(returns.getAnnualizedReturn(), 0.298, 0.001); 79 | } 80 | 81 | @Test 82 | public void testAllDebugValues() { 83 | List responses = PortfolioManagerApplication.debugOutputs(); 84 | Assertions.assertTrue(responses.get(0).contains("trades.json")); 85 | Assertions.assertTrue(responses.get(1).contains("trades.json")); 86 | Assertions.assertTrue(responses.get(2).contains("ObjectMapper")); 87 | Assertions.assertTrue(responses.get(3).contains("mainReadFile")); 88 | } 89 | 90 | @Test 91 | public void testDebugValues() { 92 | List responses = PortfolioManagerApplication.debugOutputs(); 93 | Assertions.assertTrue(responses.get(0).contains("trades.json")); 94 | } 95 | 96 | } 97 | 98 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /qmoney/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /qmoney/src/test/java/com/crio/warmup/stock/portfolio/PortfolioManagerTest.java: -------------------------------------------------------------------------------- 1 | 2 | package com.crio.warmup.stock.portfolio; 3 | 4 | import static org.mockito.ArgumentMatchers.any; 5 | import static org.mockito.ArgumentMatchers.eq; 6 | 7 | import com.crio.warmup.stock.dto.AnnualizedReturn; 8 | import com.crio.warmup.stock.dto.PortfolioTrade; 9 | import com.crio.warmup.stock.dto.TiingoCandle; 10 | import com.fasterxml.jackson.core.JsonProcessingException; 11 | import com.fasterxml.jackson.databind.ObjectMapper; 12 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 13 | import java.time.LocalDate; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | import java.util.stream.Collectors; 17 | import org.junit.jupiter.api.Assertions; 18 | import org.junit.jupiter.api.Test; 19 | import org.junit.jupiter.api.extension.ExtendWith; 20 | import org.mockito.InjectMocks; 21 | import org.mockito.Mock; 22 | import org.mockito.Mockito; 23 | import org.mockito.Spy; 24 | import org.mockito.junit.jupiter.MockitoExtension; 25 | import org.springframework.web.client.RestTemplate; 26 | 27 | 28 | /* 29 | This class is supposed to be used by assessments only. 30 | */ 31 | @ExtendWith(MockitoExtension.class) 32 | class PortfolioManagerTest { 33 | 34 | 35 | @Mock 36 | private RestTemplate restTemplate; 37 | 38 | @Spy 39 | @InjectMocks 40 | private PortfolioManagerImpl portfolioManager; 41 | 42 | private String googlQuotes = "[{\"date\":\"2019-01-02T00:00:00.000Z\",\"close\":1054.68," 43 | + "\"high\":1060.79,\"low\":1025.28,\"open\":1027.2,\"volume\":1593395,\"adjClose\":1054.68," 44 | + "\"adjHigh\":1060.79,\"adjLow\":1025.28,\"" 45 | + "adjOpen\":1027.2,\"adjVolume\":1593395,\"divCash\"" 46 | + ":0.0,\"splitFactor\":1.0},{\"date\":\"" 47 | + "2019-01-03T00:00:00.000Z\",\"close\":1025.47,\"high\"" 48 | + ":1066.26,\"low\":1022.37,\"open\":1050.67,\"volume\":2097957,\"adjClose\":1025.47," 49 | + "\"adjHigh\":1066.26,\"adjLow\":1022.37,\"adjOpen\":1050.67,\"adjVolume\":2097957," 50 | + "\"divCash\":0.0,\"splitFactor\":1.0},{\"date\":\"2019-12-12T00:00:00.000Z\"," 51 | + "\"close\":1348.49,\"high\":1080.0,\"low\":1036.86,\"open\":1042.56,\"volume\":2301428," 52 | + "\"adjClose\":1078.07,\"adjHigh\":1080.0,\"adjLow\":1036.86,\"adjOpen\":1042.56,\"adjVolume" 53 | + "\":2301428,\"divCash\":0.0,\"splitFactor\":1.0}]"; 54 | 55 | private String aaplQuotes = "[{\"date\":\"2019-01-02T00:00:00.000Z\",\"close\":157.92,\"high\":" 56 | + "158.85,\"low\":154.23,\"open\":154.89,\"volume\":37039737,\"adjClose\":155.575184502," 57 | + "\"adjHigh\":156.4913757481,\"adjLow\":151.9399740739,\"adjOpen\":152.590174313,\"adjVolume" 58 | + "\":37039737,\"divCash\":0.0,\"splitFactor\":1.0},{\"date\":\"2019-01-03T00:00:00.000Z\"," 59 | + "\"close\":142.19,\"high\":145.72,\"low\":142.0,\"open\":143.98,\"volume\":91312195," 60 | + "\"adjClose\":140.0787454682,\"adjHigh\":143.5563315959,\"adjLow\":139.8915666115,\"adjOpen" 61 | + "\":141.842167329,\"adjVolume\":91312195,\"divCash\":0.0,\"splitFactor\":1.0},{\"date\":" 62 | + "\"2019-12-12T00:00:00.000Z\",\"close\":271.46,\"high\":148.5499,\"low\":143.8,\"open" 63 | + "\":144.53,\"volume\":58607070,\"adjClose\":146.0586173649,\"adjHigh\":146.3442128942," 64 | + "\"adjLow\":141.6648399911,\"adjOpen\":142.3840008617,\"adjVolume\":58607070,\"divCash" 65 | + "\":0.0,\"splitFactor\":1.0}]"; 66 | 67 | private String msftQuotes = "[{\"date\":\"2019-01-02T00:00:00.000Z\",\"close\":101.12,\"high\"" 68 | + ":101.75,\"low\":98.94,\"open\":99.55,\"volume\":35329345,\"adjClose\":99.6386555235," 69 | + "\"adjHigh\":100.2594264193,\"adjLow\":97.490591154,\"adjOpen\":98.0916550372,\"adjVolume" 70 | + "\":35329345,\"divCash\":0.0,\"splitFactor\":1.0},{\"date\":\"2019-01-03T00:00:00.000Z\"," 71 | + "\"close\":97.4,\"high\":100.185,\"low\":97.2,\"open\":100.1,\"volume\":42578410,\"adjClose" 72 | + "\":95.9731511866,\"adjHigh\":98.7173526861,\"adjLow\":95.7760810609,\"adjOpen" 73 | + "\":98.6335978827,\"adjVolume\":42578410,\"divCash\":0.0,\"splitFactor\":1.0},{\"date\":" 74 | + "\"2019-12-12T00:00:00.000Z\",\"close\":153.24,\"high\":102.51,\"low\":98.93,\"open" 75 | + "\":99.72,\"volume\":44060620,\"adjClose\":100.4367895323,\"adjHigh\":101.0082928967," 76 | + "\"adjLow\":97.4807376477,\"adjOpen\":98.259164644,\"adjVolume\":44060620,\"divCash" 77 | + "\":0.0,\"splitFactor\":1.0}]"; 78 | 79 | @Test 80 | public void calculateExtrapolatedAnnualizedReturn() 81 | throws Exception { 82 | //given 83 | String moduleToRun = null; 84 | moduleToRun = "REFACTOR"; 85 | 86 | 87 | if (moduleToRun.equals("REFACTOR")) { 88 | Mockito.doReturn(getCandles(aaplQuotes)) 89 | .when(portfolioManager).getStockQuote(eq("AAPL"), any(), any()); 90 | Mockito.doReturn(getCandles(msftQuotes)) 91 | .when(portfolioManager).getStockQuote(eq("MSFT"), any(), any()); 92 | Mockito.doReturn(getCandles(googlQuotes)) 93 | .when(portfolioManager).getStockQuote(eq("GOOGL"), any(), any()); 94 | } 95 | PortfolioTrade trade1 = new PortfolioTrade("AAPL", 50, LocalDate.parse("2019-01-02")); 96 | PortfolioTrade trade2 = new PortfolioTrade("GOOGL", 100, LocalDate.parse("2019-01-02")); 97 | PortfolioTrade trade3 = new PortfolioTrade("MSFT", 20, LocalDate.parse("2019-01-02")); 98 | List portfolioTrades = Arrays 99 | .asList(new PortfolioTrade[]{trade1, trade2, trade3}); 100 | 101 | 102 | //when 103 | List annualizedReturns = portfolioManager 104 | .calculateAnnualizedReturn(portfolioTrades, LocalDate.parse("2019-12-12")); 105 | 106 | //then 107 | List symbols = annualizedReturns.stream().map(AnnualizedReturn::getSymbol) 108 | .collect(Collectors.toList()); 109 | Assertions.assertEquals(0.814, annualizedReturns.get(0).getAnnualizedReturn(), 0.01); 110 | Assertions.assertEquals(0.584, annualizedReturns.get(1).getAnnualizedReturn(), 0.01); 111 | Assertions.assertEquals(0.33, annualizedReturns.get(2).getAnnualizedReturn(),0.01); 112 | Assertions.assertEquals(Arrays.asList(new String[]{"AAPL", "MSFT", "GOOGL"}), symbols); 113 | 114 | } 115 | 116 | 117 | private List getCandles(String responseText) throws JsonProcessingException { 118 | ObjectMapper mapper = new ObjectMapper(); 119 | mapper.registerModule(new JavaTimeModule()); 120 | return Arrays.asList(mapper.readValue(responseText, TiingoCandle[].class)); 121 | } 122 | 123 | 124 | 125 | } 126 | 127 | -------------------------------------------------------------------------------- /qmoney/src/main/java/com/crio/warmup/stock/portfolio/PortfolioManagerImpl.java: -------------------------------------------------------------------------------- 1 | 2 | package com.crio.warmup.stock.portfolio; 3 | 4 | import static java.time.temporal.ChronoUnit.DAYS; 5 | import static java.time.temporal.ChronoUnit.SECONDS; 6 | 7 | import com.crio.warmup.stock.dto.AnnualizedReturn; 8 | import com.crio.warmup.stock.dto.Candle; 9 | import com.crio.warmup.stock.dto.PortfolioTrade; 10 | import com.crio.warmup.stock.dto.TiingoCandle; 11 | import com.fasterxml.jackson.core.JsonProcessingException; 12 | import com.fasterxml.jackson.databind.ObjectMapper; 13 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 14 | import java.time.LocalDate; 15 | import java.time.temporal.ChronoUnit; 16 | import java.util.ArrayList; 17 | import java.util.Arrays; 18 | import java.util.Collections; 19 | import java.util.Comparator; 20 | import java.util.List; 21 | import java.util.concurrent.ExecutionException; 22 | import java.util.concurrent.ExecutorService; 23 | import java.util.concurrent.Executors; 24 | import java.util.concurrent.Future; 25 | import java.util.concurrent.TimeUnit; 26 | import java.util.stream.Collectors; 27 | import java.util.stream.Stream; 28 | 29 | import javax.management.RuntimeErrorException; 30 | 31 | import org.springframework.web.client.RestTemplate; 32 | 33 | public class PortfolioManagerImpl implements PortfolioManager { 34 | 35 | 36 | 37 | 38 | private RestTemplate restTemplate; 39 | 40 | // Caution: Do not delete or modify the constructor, or else your build will 41 | // break! 42 | // Caution: Do not delete or modify the constructor, or else your build will break! 43 | // This is absolutely necessary for backward compatibility 44 | protected PortfolioManagerImpl(RestTemplate restTemplate) { 45 | this.restTemplate = restTemplate; 46 | } 47 | 48 | 49 | //TODO: CRIO_TASK_MODULE_REFACTOR 50 | // Now we want to convert our code into a module, so we will not call it from main anymore. 51 | // Copy your code from Module#3 PortfolioManagerApplication#calculateAnnualizedReturn 52 | // into #calculateAnnualizedReturn function here and make sure that it 53 | // follows the method signature. 54 | // Logic to read Json file and convert them into Objects will not be required further as our 55 | // clients will take care of it, going forward. 56 | // Test your code using Junits provided. 57 | // Make sure that all of the tests inside PortfolioManagerTest using command below - 58 | // ./gradlew test --tests PortfolioManagerTest 59 | // This will guard you against any regressions. 60 | // run ./gradlew build in order to test yout code, and make sure that 61 | // the tests and static code quality pass. 62 | 63 | //CHECKSTYLE:OFF 64 | 65 | 66 | 67 | 68 | 69 | private Comparator getComparator() { 70 | return Comparator.comparing(AnnualizedReturn::getAnnualizedReturn).reversed(); 71 | } 72 | 73 | 74 | public List calculateAnnualizedReturn(List portfolioTrades, 75 | LocalDate endDate) throws JsonProcessingException 76 | { 77 | 78 | List annualReturn = new ArrayList<>(); 79 | 80 | 81 | 82 | for (PortfolioTrade portfolioTrade : portfolioTrades) { 83 | 84 | 85 | List tiingoCandles = getStockQuote(portfolioTrade.getSymbol() , portfolioTrade.getPurchaseDate(), endDate); 86 | 87 | int no = tiingoCandles.size(); 88 | int flag=1; 89 | 90 | if(portfolioTrade.getPurchaseDate().isAfter(endDate)) 91 | { 92 | flag = 0; 93 | 94 | } 95 | 96 | 97 | LocalDate nodata; 98 | if(((tiingoCandles.get(no-1)).getDate()).equals(endDate)) 99 | { 100 | nodata = endDate; 101 | } 102 | 103 | else if(flag == 0) 104 | { 105 | nodata = LocalDate.now(); 106 | 107 | } 108 | 109 | else 110 | { 111 | nodata = tiingoCandles.get(no-1).getDate(); 112 | 113 | } 114 | 115 | 116 | double sellprice = tiingoCandles.stream() 117 | .filter(candle -> candle.getDate().equals(endDate) 118 | || candle.getDate().equals(nodata)) 119 | .findFirst().get().getClose(); 120 | 121 | //System.out.println(sellprice); 122 | double buyprice = tiingoCandles.stream() 123 | .filter(candle -> candle.getDate().equals(portfolioTrade.getPurchaseDate()) ) 124 | .findFirst().get().getOpen(); 125 | //System.out.println(buyprice); 126 | 127 | 128 | //System.out.println(buyPrice); 129 | //System.out.println(sellPrice); 130 | double totalReturn = (sellprice - buyprice) / buyprice; 131 | double totalnumdays = ChronoUnit.DAYS.between(portfolioTrade.getPurchaseDate(),endDate); 132 | //System.out.println(totalnumdays); 133 | double totalnumyears = totalnumdays / 365; 134 | double inv = 1 / totalnumyears; 135 | //System.out.println(totalnumyears); 136 | //System.out.println(totalReturn); 137 | double annualizedreturns = Math.pow((1 + totalReturn),inv) - 1; 138 | //System.out.println(annualizedreturns); 139 | 140 | AnnualizedReturn turn = new AnnualizedReturn(portfolioTrade.getSymbol(),annualizedreturns, totalReturn); 141 | 142 | annualReturn.add(turn); 143 | 144 | 145 | 146 | 147 | 148 | } 149 | annualReturn.sort(Comparator.comparing(AnnualizedReturn::getAnnualizedReturn)); 150 | Collections.reverse(annualReturn); 151 | 152 | 153 | return annualReturn; 154 | 155 | 156 | 157 | } 158 | 159 | 160 | private static ObjectMapper getObjectMapper() { 161 | ObjectMapper objectMapper = new ObjectMapper(); 162 | objectMapper.registerModule(new JavaTimeModule()); 163 | return objectMapper; 164 | } 165 | //CHECKSTYLE:OFF 166 | 167 | // TODO: CRIO_TASK_MODULE_REFACTOR 168 | // Extract the logic to call Tiingo thirdparty APIs to a separate function. 169 | // It should be split into fto parts. 170 | // Part#1 - Prepare the Url to call Tiingo based on a template constant, 171 | // by replacing the placeholders. 172 | // Constant should look like 173 | // https://api.tiingo.com/tiingo/daily//prices?startDate=?&endDate=?&token=? 174 | // Where ? are replaced with something similar to and then actual url produced by 175 | // replacing the placeholders with actual parameters. 176 | 177 | 178 | public List getStockQuote(String symbol, LocalDate from, LocalDate to) 179 | throws JsonProcessingException { 180 | 181 | ObjectMapper mapper = getObjectMapper(); 182 | //RestTemplate restTemplate = new RestTemplate(); 183 | //PortfolioManagerImpl(restTemplate); 184 | if(to.isBefore(from)) 185 | { 186 | to=LocalDate.now(); 187 | } 188 | String url = buildUri(symbol, from, to); 189 | String result = restTemplate.getForObject(url, String.class); 190 | 191 | 192 | 193 | return Arrays.asList(mapper.readValue(result, TiingoCandle[].class)); 194 | 195 | } 196 | 197 | protected String buildUri(String symbol, LocalDate startDate, LocalDate endDate) { 198 | String uriTemplate = "https://api.tiingo.com/tiingo/daily/$SYMBOL/prices?" 199 | + "startDate=$STARTDATE&endDate=$ENDDATE&token=$APIKEY"; 200 | 201 | String token = "671b3c89b9edaf3aec0390fe4ecf1b2aeb62afb5"; 202 | String url = uriTemplate.replace("$APIKEY", token).replace("$SYMBOL", symbol) 203 | .replace("$STARTDATE", startDate.toString()) 204 | .replace("$ENDDATE", endDate.toString()); 205 | 206 | 207 | 208 | return url; 209 | 210 | } 211 | 212 | 213 | // TODO: CRIO_TASK_MODULE_ADDITIONAL_REFACTOR 214 | // Modify the function #getStockQuote and start delegating to calls to 215 | // stockQuoteService provided via newly added constructor of the class. 216 | // You also have a liberty to completely get rid of that function itself, however, make sure 217 | // that you do not delete the #getStockQuote function. 218 | 219 | } 220 | -------------------------------------------------------------------------------- /__CRIO__/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 57 | 59 | 60 | 61 | 62 | 63 | 65 | 66 | 67 | 69 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 116 | 117 | 118 | 119 | 121 | 122 | 123 | 125 | 126 | 127 | 128 | 130 | 131 | 132 | 133 | 135 | 136 | 137 | 138 | 140 | 141 | 142 | 143 | 144 | 146 | 147 | 148 | 149 | 151 | 152 | 153 | 154 | 156 | 157 | 158 | 159 | 160 | 162 | 164 | 166 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 213 | 214 | 215 | 216 | 217 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | -------------------------------------------------------------------------------- /qmoney/src/main/java/com/crio/warmup/stock/PortfolioManagerApplication.java: -------------------------------------------------------------------------------- 1 | 2 | package com.crio.warmup.stock; 3 | 4 | import com.crio.warmup.stock.dto.AnnualizedReturn; 5 | import com.crio.warmup.stock.dto.PortfolioTrade; 6 | import com.crio.warmup.stock.dto.TiingoCandle; 7 | import com.crio.warmup.stock.dto.TotalReturnsDto; 8 | import com.crio.warmup.stock.log.UncaughtExceptionHandler; 9 | import com.crio.warmup.stock.portfolio.PortfolioManager; 10 | import com.crio.warmup.stock.portfolio.PortfolioManagerFactory; 11 | import com.fasterxml.jackson.databind.ObjectMapper; 12 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.net.URISyntaxException; 16 | import java.nio.file.Files; 17 | import java.nio.file.Paths; 18 | import java.time.LocalDate; 19 | import java.time.temporal.ChronoUnit; 20 | import java.util.ArrayList; 21 | import java.util.Arrays; 22 | import java.util.Collections; 23 | import java.util.Comparator; 24 | import java.util.List; 25 | import java.util.UUID; 26 | import java.util.logging.Level; 27 | import java.util.logging.Logger; 28 | import java.util.stream.Collectors; 29 | import java.util.stream.Stream; 30 | import org.apache.logging.log4j.ThreadContext; 31 | import org.springframework.web.client.RestTemplate; 32 | 33 | 34 | public class PortfolioManagerApplication { 35 | 36 | // TODO: CRIO_TASK_MODULE_JSON_PARSING 37 | // Read the json file provided in the argument[0]. The file will be avilable in the classpath. 38 | // 1. Use #resolveFileFromResources to get actual file from classpath. 39 | // 2. parse the json file using ObjectMapper provided with #getObjectMapper, 40 | // and extract symbols provided in every trade. 41 | // return the list of all symbols in the same order as provided in json. 42 | // Test the function using gradle commands below 43 | // ./gradlew run --args="trades.json" 44 | // Make sure that it prints below String on the console - 45 | // ["AAPL","MSFT","GOOGL"] 46 | // Now, run 47 | // ./gradlew build and make sure that the build passes successfully 48 | // There can be few unused imports, you will need to fix them to make the build pass. 49 | 50 | public static List mainReadFile(String[] args) throws IOException, URISyntaxException { 51 | 52 | File file = resolveFileFromResources(args[0]); 53 | byte[] byteArray = Files.readAllBytes(file.toPath()); 54 | String content = new String(byteArray, "UTF8"); 55 | 56 | ObjectMapper mapper = getObjectMapper(); 57 | PortfolioTrade[] trades = mapper.readValue(content, PortfolioTrade[].class); 58 | 59 | List symbols = new ArrayList<>(); 60 | 61 | for (int i = 0; i < trades.length; i++) { 62 | symbols.add(trades[i].getSymbol()); 63 | } 64 | 65 | return symbols; 66 | 67 | } 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | private static void printJsonObject(Object object) throws IOException { 76 | Logger logger = Logger.getLogger(PortfolioManagerApplication.class.getCanonicalName()); 77 | ObjectMapper mapper = new ObjectMapper(); 78 | logger.info(mapper.writeValueAsString(object)); 79 | } 80 | 81 | private static File resolveFileFromResources(String filename) throws URISyntaxException { 82 | return Paths.get( 83 | Thread.currentThread().getContextClassLoader().getResource(filename).toURI()).toFile(); 84 | } 85 | 86 | private static ObjectMapper getObjectMapper() { 87 | ObjectMapper objectMapper = new ObjectMapper(); 88 | objectMapper.registerModule(new JavaTimeModule()); 89 | return objectMapper; 90 | } 91 | 92 | 93 | // TODO: CRIO_TASK_MODULE_JSON_PARSING 94 | // Follow the instructions provided in the task documentation and fill up the correct values for 95 | // the variables provided. First value is provided for your reference. 96 | // A. Put a breakpoint on the first line inside mainReadFile() which says 97 | // return Collections.emptyList(); 98 | // B. Then Debug the test #mainReadFile provided in PortfoliomanagerApplicationTest.java 99 | // following the instructions to run the test. 100 | // Once you are able to run the test, perform following tasks and record the output as a 101 | // String in the function below. 102 | // Use this link to see how to evaluate expressions - 103 | // https://code.visualstudio.com/docs/editor/debugging#_data-inspection 104 | // 1. evaluate the value of "args[0]" and set the value 105 | // to the variable named valueOfArgument0 (This is implemented for your reference.) 106 | // 2. In the same window, evaluate the value of expression below and set it 107 | // to resultOfResolveFilePathArgs0 108 | // expression ==> resolveFileFromResources(args[0]) 109 | // 3. In the same window, evaluate the value of expression below and set it 110 | // to toStringOfObjectMapper. 111 | // You might see some garbage numbers in the output. Dont worry, its expected. 112 | // expression ==> getObjectMapper().toString() 113 | // 4. Now Go to the debug window and open stack trace. Put the name of the function you see at 114 | // second place from top to variable functionNameFromTestFileInStackTrace 115 | // 5. In the same window, you will see the line number of the function in the stack trace window. 116 | // assign the same to lineNumberFromTestFileInStackTrace 117 | // Once you are done with above, just run the corresponding test and 118 | // make sure its working as expected. use below command to do the same. 119 | // ./gradlew test --tests PortfolioManagerApplicationTest.testDebugValues 120 | 121 | public static List debugOutputs() { 122 | 123 | String valueOfArgument0 = "trades.json"; 124 | String resultOfResolveFilePathArgs0 = 125 | "/home/crio-user/workspace/vanshikagarg17-ME_QMONEY/qmoney/bin/main/trades.json"; 126 | String toStringOfObjectMapper = "com.fasterxml.jackson.databind.ObjectMapper@6d9f7a80"; 127 | String functionNameFromTestFileInStackTrace = "PortfolioManagerApplicationTest.mainReadFile()"; 128 | String lineNumberFromTestFileInStackTrace = "22"; 129 | 130 | 131 | return Arrays.asList(new String[]{valueOfArgument0, resultOfResolveFilePathArgs0, 132 | toStringOfObjectMapper, functionNameFromTestFileInStackTrace, 133 | lineNumberFromTestFileInStackTrace}); 134 | } 135 | // TODO: CRIO_TASK_MODULE_REST_API 136 | // Copy the relavent code from #mainReadFile to parse the Json into PortfolioTrade list. 137 | // Now That you have the list of PortfolioTrade already populated in module#1 138 | // For each stock symbol in the portfolio trades, 139 | // Call Tiingo api (https://api.tiingo.com/tiingo/daily//prices?startDate=&endDate=&token=) 140 | // with 141 | // 1. ticker = symbol in portfolio_trade 142 | // 2. startDate = purchaseDate in portfolio_trade. 143 | // 3. endDate = args[1] 144 | // Use RestTemplate#getForObject in order to call the API, 145 | // and deserialize the results in List 146 | // Note - You may have to register on Tiingo to get the api_token. 147 | // Please refer the the module documentation for the steps. 148 | // Find out the closing price of the stock on the end_date and 149 | // return the list of all symbols in ascending order by its close value on endDate 150 | // Test the function using gradle commands below 151 | // ./gradlew run --args="trades.json 2020-01-01" 152 | // ./gradlew run --args="trades.json 2019-07-01" 153 | // ./gradlew run --args="trades.json 2019-12-03" 154 | // And make sure that its printing correct results. 155 | 156 | public static List mainReadQuotes(String[] args) throws IOException, URISyntaxException { 157 | 158 | File file = resolveFileFromResources(args[0]); 159 | // DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); 160 | // LocalDate endDate = LocalDate.parse(args[1],formatter); 161 | LocalDate endDate = LocalDate.parse(args[1]); 162 | byte[] byteArray = Files.readAllBytes(file.toPath()); 163 | String content = new String(byteArray, "UTF8"); 164 | 165 | ObjectMapper mapper = getObjectMapper(); 166 | PortfolioTrade[] portfolioTrades = mapper.readValue(content, PortfolioTrade[].class); 167 | 168 | String token = "8ff4e2051f46223ce98c00d6337906fe64fe33b7"; 169 | String uri = "https://api.tiingo.com/tiingo/daily/$SYMBOL/prices?startDate=$STARTDATE&endDate=$ENDDATE&token=$APIKEY"; 170 | 171 | List totalReturnsDtoList = new ArrayList<>(); 172 | 173 | for (PortfolioTrade portfolioTrade : portfolioTrades) { 174 | 175 | String url = uri.replace("$APIKEY", token).replace("$SYMBOL", portfolioTrade.getSymbol()) 176 | .replace("$STARTDATE", portfolioTrade.getPurchaseDate().toString()) 177 | .replace("$ENDDATE", endDate.toString()); 178 | 179 | // TiingoCandle[] tiingoCandles = new 180 | // RestTemplate().getForObject(url,TiingoCandle[].class); 181 | 182 | RestTemplate restTemplate = new RestTemplate(); 183 | String result = restTemplate.getForObject(url, String.class); 184 | TiingoCandle[] tiingoCandles = mapper.readValue(result, TiingoCandle[].class); 185 | 186 | // List tiingoCandles = mapper.readValue(result, 187 | // ArrayList()); 188 | 189 | double sum = Stream.of(tiingoCandles) 190 | .filter(candle -> candle.getDate().equals(endDate) 191 | || candle.getDate().equals(endDate.minusDays(1))).findFirst().get() 192 | .getClose(); 193 | 194 | TotalReturnsDto totalReturn = new TotalReturnsDto(portfolioTrade.getSymbol(), sum); 195 | 196 | totalReturnsDtoList.add(totalReturn); 197 | 198 | } 199 | totalReturnsDtoList.sort(Comparator.comparing(TotalReturnsDto::getClosingPrice)); 200 | List symbols = new ArrayList<>(); 201 | 202 | for (TotalReturnsDto a : totalReturnsDtoList) { 203 | symbols.add(a.getSymbol()); 204 | } 205 | 206 | return symbols; 207 | 208 | } 209 | 210 | 211 | // TODO: CRIO_TASK_MODULE_CALCULATIONS 212 | // Copy the relevant code from #mainReadQuotes to parse the Json into PortfolioTrade list and 213 | // Get the latest quotes from TIingo. 214 | // Now That you have the list of PortfolioTrade And their data, 215 | // With this data, Calculate annualized returns for the stocks provided in the Json 216 | // Below are the values to be considered for calculations. 217 | // buy_price = open_price on purchase_date and sell_value = close_price on end_date 218 | // startDate and endDate are already calculated in module2 219 | // using the function you just wrote #calculateAnnualizedReturns 220 | // Return the list of AnnualizedReturns sorted by annualizedReturns in descending order. 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | // TODO: CRIO_TASK_MODULE_REFACTOR 241 | // Once you are done with the implementation inside PortfolioManagerImpl and 242 | // PortfolioManagerFactory, 243 | // Create PortfolioManager using PortfoliomanagerFactory, 244 | // Refer to the code from previous modules to get the List and endDate, and 245 | // call the newly implemented method in PortfolioManager to calculate the annualized returns. 246 | // Test the same using the same commands as you used in module 3 247 | // use gralde command like below to test your code 248 | // ./gradlew run --args="trades.json 2020-01-01" 249 | // ./gradlew run --args="trades.json 2019-07-01" 250 | // ./gradlew run --args="trades.json 2019-12-03" 251 | // where trades.json is your json file 252 | 253 | public static List mainCalculateSingleReturn(String[] args) 254 | throws IOException, URISyntaxException { 255 | 256 | File file = resolveFileFromResources(args[0]); 257 | LocalDate endDate = LocalDate.parse(args[1]); 258 | byte[] byteArray = Files.readAllBytes(file.toPath()); 259 | String content = new String(byteArray, "UTF8"); 260 | 261 | ObjectMapper mapper = getObjectMapper(); 262 | PortfolioTrade[] portfolioTrades = mapper.readValue(content, PortfolioTrade[].class); 263 | 264 | String token = "671b3c89b9edaf3aec0390fe4ecf1b2aeb62afb5"; 265 | String uri = "https://api.tiingo.com/tiingo/daily/$SYMBOL/prices?startDate=$STARTDATE&endDate=$ENDDATE&token=$APIKEY"; 266 | 267 | List annualReturn = new ArrayList<>(); 268 | 269 | for (PortfolioTrade portfolioTrade : portfolioTrades) { 270 | 271 | String url = uri.replace("$APIKEY", token).replace("$SYMBOL", portfolioTrade.getSymbol()) 272 | .replace("$STARTDATE", portfolioTrade.getPurchaseDate().toString()) 273 | .replace("$ENDDATE", endDate.toString()); 274 | 275 | 276 | RestTemplate restTemplate = new RestTemplate(); 277 | String result = restTemplate.getForObject(url, String.class); 278 | TiingoCandle[] tiingoCandles = mapper.readValue(result, TiingoCandle[].class); 279 | 280 | double buyprice = Stream.of(tiingoCandles) 281 | .filter(candle -> candle.getDate().equals(portfolioTrade.getPurchaseDate())) 282 | .findFirst().get().getOpen(); 283 | 284 | double sellprice = Stream.of(tiingoCandles) 285 | .filter(candle -> candle.getDate().equals(endDate)) 286 | .findFirst().get().getClose(); 287 | 288 | AnnualizedReturn turn = 289 | calculateAnnualizedReturns(endDate,portfolioTrade,buyprice,sellprice); 290 | 291 | annualReturn.add(turn); 292 | 293 | } 294 | 295 | annualReturn.sort(Comparator.comparing(AnnualizedReturn::getAnnualizedReturn)); 296 | Collections.reverse(annualReturn); 297 | 298 | return annualReturn; 299 | 300 | } 301 | 302 | // TODO: CRIO_TASK_MODULE_CALCULATIONS 303 | // annualized returns should be calculated in two steps - 304 | // 1. Calculate totalReturn = (sell_value - buy_value) / buy_value 305 | // Store the same as totalReturns 306 | // 2. calculate extrapolated annualized returns by scaling the same in years span. The formula is 307 | // annualized_returns = (1 + total_returns) ^ (1 / total_num_years) - 1 308 | // Store the same as annualized_returns 309 | // return the populated list of AnnualizedReturn for all stocks, 310 | // Test the same using below specified command. The build should be successful 311 | // ./gradlew test --tests PortfolioManagerApplicationTest.testCalculateAnnualizedReturn 312 | 313 | public static AnnualizedReturn calculateAnnualizedReturns(LocalDate endDate, 314 | PortfolioTrade trade, Double buyPrice, Double sellPrice) { 315 | 316 | double totalReturn = (sellPrice - buyPrice) / buyPrice; 317 | double totalnumdays = ChronoUnit.DAYS.between(trade.getPurchaseDate(),endDate); 318 | double totalnumyears = totalnumdays / 365; 319 | double inv = 1 / totalnumyears; 320 | 321 | double annualizedreturns = Math.pow((1 + totalReturn),inv) - 1; 322 | 323 | return new AnnualizedReturn(trade.getSymbol(),annualizedreturns, totalReturn); 324 | } 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | // Confirm that you are getting same results as in Module3. 336 | 337 | public static List mainCalculateReturnsAfterRefactor(String[] args) 338 | throws Exception { 339 | 340 | 341 | File file = resolveFileFromResources(args[0]); 342 | LocalDate endDate; 343 | if (args.length == 1) { 344 | 345 | endDate = LocalDate.now().minusDays(3); 346 | 347 | } else { 348 | 349 | endDate = LocalDate.parse(args[1]); 350 | 351 | } 352 | byte[] byteArray = Files.readAllBytes(file.toPath()); 353 | String contents = new String(byteArray, "UTF8"); 354 | ObjectMapper objectMapper = getObjectMapper(); 355 | RestTemplate restTemplate = new RestTemplate(); 356 | 357 | PortfolioTrade[] portfolioTrades = objectMapper.readValue(contents, PortfolioTrade[].class); 358 | PortfolioManagerFactory shape = new PortfolioManagerFactory(); 359 | PortfolioManager portfolioManager = shape.getPortfolioManager(restTemplate); 360 | 361 | return portfolioManager.calculateAnnualizedReturn(Arrays.asList(portfolioTrades), endDate); 362 | } 363 | 364 | 365 | private static String readFileAsString(String file) { 366 | return null; 367 | } 368 | 369 | 370 | public static void main(String[] args) throws Exception { 371 | Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler()); 372 | ThreadContext.put("runId", UUID.randomUUID().toString()); 373 | 374 | 375 | 376 | 377 | printJsonObject(mainCalculateReturnsAfterRefactor(args)); 378 | } 379 | } 380 | 381 | -------------------------------------------------------------------------------- /__CRIO__/CrioEditorStyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 20 | 26 | 33 | --------------------------------------------------------------------------------