diff --git a/.gitignore b/.gitignore index f69985ef1f..67d574d554 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ bin/ /text-ui-test/ACTUAL.txt text-ui-test/EXPECTED-UNIX.TXT + +data/*.ser diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000000..18854876c6 --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-midnight \ No newline at end of file diff --git a/build.gradle b/build.gradle index b0c5528fb5..8cb2e87a80 100644 --- a/build.gradle +++ b/build.gradle @@ -7,11 +7,15 @@ plugins { repositories { mavenCentral() + maven { + url "https://dl.bintray.com/patriques82/maven" + } } dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0' testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0' + compile 'org.patriques:alphavantage4j:1.4' } test { @@ -29,11 +33,11 @@ test { } application { - mainClassName = "seedu.duke.Duke" + mainClassName = "seedu.duke.PaperTrade" } shadowJar { - archiveBaseName = "duke" + archiveBaseName = "paperTrade" archiveClassifier = null } @@ -41,6 +45,7 @@ checkstyle { toolVersion = '8.23' } -run{ +run { standardInput = System.in + enableAssertions = true } diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..4238f2bd07 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -2,8 +2,8 @@ Display | Name | Github Profile | Portfolio --------|:----:|:--------------:|:---------: -![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +![](https://via.placeholder.com/100.png?text=Photo) | Alvin Tan De Jun | [Github](https://github.com/trolommonm) | [Portfolio](team/trolommonm.md) +![](https://via.placeholder.com/100.png?text=Photo) | Woon Yoke Min | [Github](https://github.com/yokemin) | [Portfolio](team/yokemin.md) +![](https://via.placeholder.com/100.png?text=Photo) | Wang Liang Yi | [Github](https://github.com/wly99) | [Portfolio](team/wly99.md) +![](https://via.placeholder.com/100.png?text=Photo) | Ang Jun Xian | [Github](https://github.com/JunxianAng) | [Portfolio](team/junxian.md) +![](https://via.placeholder.com/100.png?text=Photo) | Chan Shao Jing | [Github](https://github.com/shaojingle) | [Portfolio](team/shaojing.md) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 0ec3db103d..d520502279 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,29 +1,214 @@ # Developer Guide +It is recommended that you read through the user guide to familiarize yourself with the program before using the +developer guide. + +# Setting up PaperTrade +Fork the repository and clone the fork into your computer. + +To set up the project: +1. Ensure that you have JDK 11 installed. +2. Import the project as a Gradle project. +3. To build the project, run "./gradlew build" on a Unix machine or run "gradlew build" on Windows. ## Design & implementation -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +### Architecture + +![](./diagrams/Packages.png) + +The Package Diagram above gives a high level view of the project structure and the classes in each package. + +![](./diagrams/Architecture.png) + +The High Level Class Diagram above gives a high level design of PaperTrade. Below is a quick overview of each component. + +### Overview + +* `PaperTrade`: The main entry point into the program. It initializes the Controller object which then takes over the +execution of the program. +* `Controller`: Acts as an interface between all the other classes, to process all the business logic incoming commands +from the user. Manipulates the data through `PortfolioManager` and interact with `Ui` to display information to the +user. +* `PortfolioManager`: Responsible for managing the persistency of the user's data through the `Storage` class and holds +all the data for the program at a point in time. +* `Ui`: Responsible for displaying information to the user and getting input from the user. +* `Parser`: Responsible for parsing the user input and returning a `Command` object corresponding to the user command +requested. +* `StockPriceFetcher`: Responsible for calling the AlphaVantage API to retrieve stock information. +* `Storage`: Responsible for managing the storing of data for persistency. + +### Lifecycle of PaperTrade +The sequence diagram below shows how different packages and classes interact with each other throughout the lifecycle +of PaperTrade. + +![](./diagrams/Lifecycle.png) + +## Implementation + +### Buy/Sell Stock Feature + +#### Current implementation + +Buy and sell stock commands are largely similar, with the only difference being the way values of attributes in +the objects instantiated are updated and the condition for throwing exceptions. +Other than that, the way the functions work is the same. + +Below is the explanation of the implementation of based on buy stock command, +which can be applied to the sell stock command as well. + +Given below is an example usage scenario and how buy stock command behaves at each step. + +![](./diagrams/BuyStockState0.png) + +**Step 1** : The user calls the buy stock command from the `Parser`. +Buy stock command is instantiated by `Parser`, which calls the `buyParse` method to get the attribute values +for `symbol` and `quantity` required to instantiate `buyCommand`. The `Controller` instantiates `Ui`, `PortfolioManager` +and `StockPriceFetcher`, which gets the `price` of stock. +In the `Controller`, we check for the instance of `buyCommand` object and calls the `buyStock` method. + +Below is a table of what each parameter corresponds to in the state diagram of the program. + +|Parameter|Corresponds to +|:---:|:---: +|`symbol`| Ticker symbol of Stock to buy +|`quantity`| Integer number of shares to be +|`price`| Price of stock at current time + +**Step 2** : `buyStock()` is called from the `PortfolioManager` with the values of `symbol` and `quantity` +and `price` passed to them. + +**Step 3** : `Portfolio` is instantiated and its `buyStock` method is called as well. + + ![](./diagrams/BuyStockState1.png) + + +**Step 4** : `Wallet` and `Stock` are instantiated. +It then checks if there is sufficient fund in the wallet. If so, `buyStock` method is called from the `wallet` instance. +Then, new `Transaction` object is instantiated. +The `transaction` object stores details of the stock bought. +Below is a table of what each attribute in `Transaction` corresponds to in the program. + +|Attribute|Corresponds to +|:---:|:---: +|`TransactionType`| Buy or Sell stock +|`Quantity`| Integer number of shares to be bought +|`BuyPrice`| Cost price of a stock at a specific time +|`LocalDateTime`| the time when the command is called + +**Step 5** : Following that, _if a stock with the same symbol has not been instantiated before_, +a new 'Stock' object is also instantiated. Otherwise, the `Stock` object of that stock symbol will be used. + +The method `addTransaction` in the `stock` object is then called, with the `transaction` object as a parameter, +to update the value of the attribute `totalQuantity` in `Stock`. + +![](./diagrams/BuyStockState2.png) + +**Step 6** : `Portfolio`, `Wallet`, `Transaction` and `Stock` are terminated first. +The `save` method is then called. + +**Step 7**: `Controller` then calls the relevant methods from `Ui` to print the information about the stock bought and +the amount left in the wallet. `StockPriceFetcher`, `Ui` and `PortfolioManager` are then terminated. + +![](./diagrams/BuyStockState3.png) + +The following sequence diagram summarizes what happens when the user executes an `BuyCommand` : + +![](./diagrams/BuyStockSequence.png) + +#### Design consideration + +The following explains the design considerations when implementing commands: + +* Make `BuyCommand` As a class by itself + * Reason: Increases modularity of code, higher overall code quality +* Alternatives: have a `buyCommand` method, increases coupling and reduces testability + +### View Portfolio feature + +#### Current implementation + +View portfolio command is executed by `Controller`. It allows users to access, retrieve and view array list of stocks +owned and their historical transactions. This is done by instantiating a new `PortfolioManager` which is able to +access `Portfolio`. The `Portfolio` object encapsulates `Stock` and `Transaction`. + +Addtionally, `Stock` object is able to retrieve latest live prices by instantiating `StockPriceFetcher` to call +AlphaVantage API. This allows users to view not only their stocks historial transactions, but to see their profit/loss +based on current latest price. + +Given below is an example usage scenario and how view portfolio command behaves at each step. + +![](./diagrams/ViewPortfolioState0.png) + +**Step 1**: `Parser` will initialise `ViewCommand` and call `viewPortfolio()` command from the `Controller`. `Ui` is initialised to call `view()`. The method +takes in an parameter of an array list of stock to be displayed. + +|Parameter|Corresponds to +|:---:|:---: +|`Stock`| Stock objects + +**Step 2**: To obtain an array list of stock to be used as an arugment in `Ui` `view()` method, `PortfolioManager` is initialised to call `getAllStocks()` method. +The method returns an array list of `Stock` by initialising `Portfolio` which is keeps a HashMap of `Stock` objects. Below is a table of what each attribute in +`Stock`corresponds to in the program. + +|Attribute|Corresponds to +|:---:|:---: +|`Symbol`| Ticker Symbol of Stock in possession +|`totalQuantity`| Integer number of shares currently owned +|`transactions`| An array list of `Transaction` object + +![](./diagrams/ViewPortfolioState1.png) + +**Step 3**: For each of the `Stock` object to be displayed, `getTransaction()` method is called to obtained historial +records of user's stock transactions. +Below is a table of what each attribute in `Transaction` corresponds to in the program. + +|Attribute|Corresponds to +|:---:|:---: +|`TransactionType`| Buy or Sell stock +|`Quantity`| Integer number of shares to be bought +|`BuyPrice`| Cost price of a stock at a specific time +|`LocalDateTime`| the time when the command is called + +**Step 4**: For each of the `Stock` object to be displayed, `getLatestPrice()` method is called by instantiating a new `StockPriceFetcher` +which calls out to AlphaVantage API to obtain latest stock price. + +User's latest profit/loss will be displayed through calculation of latest stock price +against historical buy price. + +![](./diagrams/ViewPortfolioState2.png) + +**Step 5**: `Parser`, `ViewCommand`, `Stock`, `Transaction`, `StockPriceFetcher` are terminated. ## Product scope ### Target user profile -{Describe the target user profile} +We are targeting people below 25 who have never traded stocks before. ### Value proposition {Describe the value proposition: what problem does it solve?} +Paper trading allows inexperienced people to get a feel of what trading feels like so that they can used to it without the downside of losing real money. ## User Stories -|Version| As a ... | I want to ... | So that I can ...| +|Version| As a/an ... | I want to ... | So that I can ...| |--------|----------|---------------|------------------| -|v1.0|new user|see usage instructions|refer to them when I forget how to use the application| -|v2.0|user|find a to-do item by name|locate a to-do without having to go through the entire list| +|v1.0|new investor|see usage instructions|refer to them when I forget how to use the application| +|v1.0|new investor|trade without putting my money at risk|learn from my mistakes and experience without losing money| +|v1.0|investor|search for stocks I can buy|have more information to make a more informed decision| +|v1.0|investor|buy stocks|profit from any capital gains or dividends| +|v1.0|investor|sell stocks|realise my gains or reallocate my money to other stocks| +|v1.0|investor|view my portfolio|see what stocks I have and my past transactions| +|v2.0|investor|keep track of what stocks I have bought or sold|see how much money I've made or lost| +|v2.0|investor|see how much cash I have left|decide how much to buy or sell| +|v2.0|investor|have a watchlist of stocks|track the price movements of individual stocks| +|v2.1|investor|see the performance of my portfolio|see if I'm on track for my financial goals| ## Non-Functional Requirements -{Give non-functional requirements} +1. Program should not take more than 5s to run for every command. +2. Program should give some loading indicator when fetching stock prices from API calls. ## Glossary diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..298f7bdc2e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,8 +1,6 @@ -# Duke - -{Give product intro here} +# Paper trading command line app Useful links: -* [User Guide](UserGuide.md) -* [Developer Guide](DeveloperGuide.md) -* [About Us](AboutUs.md) +* [User Guide](UserGuide.md)(Read more about how to use our application!) +* [Developer Guide](DeveloperGuide.md)(Good to read for devs working on our app!) +* [About Us](AboutUs.md)(The awesome team behind this!) diff --git a/docs/UML/Architecture.puml b/docs/UML/Architecture.puml new file mode 100644 index 0000000000..5e841019e7 --- /dev/null +++ b/docs/UML/Architecture.puml @@ -0,0 +1,18 @@ +@startuml +rectangle PaperTrade +rectangle Controller +rectangle Ui +rectangle PortfolioManager +rectangle Parser +rectangle StockPriceFetcher +rectangle Portfolio +rectangle Wallet + +PaperTrade --> Controller +Controller --> Ui +Controller --> PortfolioManager +PortfolioManager --> Portfolio +PortfolioManager --> Wallet +Controller --> Parser +Controller --> StockPriceFetcher +@enduml \ No newline at end of file diff --git a/docs/UML/BuyStockSequence.puml b/docs/UML/BuyStockSequence.puml new file mode 100644 index 0000000000..68e4ac5952 --- /dev/null +++ b/docs/UML/BuyStockSequence.puml @@ -0,0 +1,75 @@ +@startuml +participant ":Controller" as Controller +participant ":StockPriceFetcher" as StockPriceFetcher +participant ":PortfolioManager" as PortfolioManager +participant ":Portfolio" as Portfolio +participant ":Wallet" as Wallet +participant ":Transaction" as Transaction +participant ":Stock" as Stock + + +[-> Controller : buyStock(symbol, quantity) +activate Controller + + +Controller -> StockPriceFetcher : fetchLatestPrice(symbol) +activate StockPriceFetcher + +StockPriceFetcher --> Controller : price +deactivate StockPriceFetcher + + +Controller -> PortfolioManager : buyStock(symbol, quantity, price) +activate PortfolioManager + +PortfolioManager -> Portfolio : buyStock(symbol, quantity, price) +activate Portfolio + +Portfolio -> Wallet : buyStock(quantity, price) +activate Wallet + +Wallet --> Portfolio : +deactivate Wallet + +create Transaction +Portfolio -> Transaction : new Transaction(quantity, price, time) +activate Transaction + +Transaction --> Portfolio : transaction +deactivate Transaction + + +alt stock not in portfolio + create Stock + Portfolio -> Stock : new Stock(symbol) + activate Stock + Stock --> Portfolio + deactivate Stock + Portfolio -> Stock : addTransaction(transaction) + activate Stock + Stock --> Portfolio : + deactivate Stock + +else stock already exist in portfolio + Portfolio -> Stock : addTransaction(transaction) + activate Stock + Stock --> Portfolio : + deactivate Stock + +end + +Portfolio --> PortfolioManager : +deactivate Portfolio + +PortfolioManager -> PortfolioManager : save() +activate PortfolioManager +PortfolioManager --> PortfolioManager : +deactivate PortfolioManager + +PortfolioManager --> Controller : +deactivate PortfolioManager + +[<--Controller +deactivate Controller + +@enduml \ No newline at end of file diff --git a/docs/UML/BuyStockState0.puml b/docs/UML/BuyStockState0.puml new file mode 100644 index 0000000000..291722211a --- /dev/null +++ b/docs/UML/BuyStockState0.puml @@ -0,0 +1,27 @@ +@startuml + +hide footbox +hide members +hide circle +skinparam packageStyle Rectangle +skinparam ClassFontColor #000000 +skinparam ClassBorderColor #000000 + +title Initial state + +package States { + class State1 as "__:PortfolioManager__" + class State2 as "__:Ui__" + class State3 as "__buyCommand:BuyCommand__" + class State5 as "__stockPriceFetcher:StockPriceFetcher__" +} + +class Pointer as "Controller" #FFFFF +Pointer -down-> State1 +Pointer -down-> State2 +Pointer -down-> State5 + +class Pointer2 as "Parser" #FFFFF +Pointer2 -down-> State3 + +@enduml \ No newline at end of file diff --git a/docs/UML/BuyStockState1.puml b/docs/UML/BuyStockState1.puml new file mode 100644 index 0000000000..9fb8e7656c --- /dev/null +++ b/docs/UML/BuyStockState1.puml @@ -0,0 +1,31 @@ +@startuml + +hide footbox +hide members +hide circle +skinparam packageStyle Rectangle +skinparam ClassFontColor #000000 +skinparam ClassBorderColor #000000 + +title State after Step 3 + +package States { + class State1 as "__:PortfolioManager__" + class State2 as "__:Ui__" + class State3 as "__buyCommand:BuyCommand__" + class State4 as "__portfolio:Portfolio__" + class State5 as "__stockPriceFetcher:StockPriceFetcher__" + class State6 as "__:Wallet__" + +} +hide State6 +State1 -down-> State4 + +class Pointer as "Controller" #FFFFF +Pointer -down-> State1 +Pointer -down-> State2 +Pointer -down-> State5 +class Pointer2 as "Parser" #FFFFF +Pointer2 -down-> State3 + +@enduml \ No newline at end of file diff --git a/docs/UML/BuyStockState2.puml b/docs/UML/BuyStockState2.puml new file mode 100644 index 0000000000..272913a932 --- /dev/null +++ b/docs/UML/BuyStockState2.puml @@ -0,0 +1,34 @@ +@startuml + +hide footbox +hide members +hide circle +skinparam packageStyle Rectangle +skinparam ClassFontColor #000000 +skinparam ClassBorderColor #000000 + +title State after Step:5 + +package States { + class State1 as "__:PortfolioManager__" + class State2 as "__:Ui__" + class State3 as "__buyCommand:BuyCommand__" + class State4 as "__portfolio:Portfolio__" + class State5 as "__stockPriceFetcher:StockPriceFetcher__" + class State6 as "__wallet:Wallet__" + class State7 as "__transaction:Transaction__" + class State8 as "__stock:Stock__" +} +State1 -down-> State4 +State4 -down-> State6 +State4 -down-> State7 +State4 -down-> State8 + +class Pointer as "Controller" #FFFFF +Pointer -down-> State1 +Pointer -down-> State2 +Pointer -down-> State5 +class Pointer2 as "Parser" #FFFFF +Pointer2 -down-> State3 + +@enduml \ No newline at end of file diff --git a/docs/UML/BuyStockState3.puml b/docs/UML/BuyStockState3.puml new file mode 100644 index 0000000000..12592c463b --- /dev/null +++ b/docs/UML/BuyStockState3.puml @@ -0,0 +1,20 @@ +@startuml + +hide footbox +hide members +hide circle +skinparam packageStyle Rectangle +skinparam ClassFontColor #000000 +skinparam ClassBorderColor #000000 + +title Final State + +package States { + + class State3 as "__buyCommand:BuyCommand__" +} + +class Pointer2 as "Parser" #FFFFF +Pointer2 -down-> State3 + +@enduml \ No newline at end of file diff --git a/docs/UML/Lifecycle.puml b/docs/UML/Lifecycle.puml new file mode 100644 index 0000000000..e9db6c6497 --- /dev/null +++ b/docs/UML/Lifecycle.puml @@ -0,0 +1,55 @@ +@startuml +actor User +participant ":PaperTrade" as PaperTrade +participant ":Controller" as Controller +participant ":Ui" as Ui +participant ":Parser" as Parser + +User -> PaperTrade : main() +activate PaperTrade + +create Controller +PaperTrade -> Controller : new Controller() +activate Controller + +create Ui +Controller -> Ui : new Ui() +activate Ui +Controller <-- Ui +deactivate Ui + +PaperTrade <-- Controller +deactivate Controller + +PaperTrade -> Controller : runApp() + +activate Controller +Controller -> Ui : greetUser() +activate Ui +Controller <-- Ui +deactivate Ui + +loop until user quits + Controller -> Ui : getUserInput() + activate Ui + Controller <-- Ui : userInput + deactivate Ui + + Controller -> Parser : class method parseCommand(userInput) + activate Parser + Controller <-- Parser : command + deactivate Parser + + Controller -> Controller : executeCommand(command) + activate Controller + Controller <-- Controller + deactivate Controller +end + +PaperTrade <-- Controller + +deactivate Controller +User <-- PaperTrade : on exit +deactivate PaperTrade + +@enduml \ No newline at end of file diff --git a/docs/UML/Packages.puml b/docs/UML/Packages.puml new file mode 100644 index 0000000000..c41227aa0c --- /dev/null +++ b/docs/UML/Packages.puml @@ -0,0 +1,39 @@ +@startuml +left to right direction + +package controller { +class Controller + +Controller --> api.StockPriceFetcher +Controller --> model.PortfolioManager +Controller --> parser.Parser +Controller --> ui.Ui +} + +package command { +abstract class Command +class BuyCommand extends Command +class SellCommand extends Command +class ByeCommand extends Command +class InvalidCommand extends Command +class SearchCommand extends Command +class ViewCommand extends Command +class WalletCommand extends Command +} + +package model { +class Portfolio +class PortfolioManager +class Stock +class Transaction +enum TransactionType +class Wallet + +PortfolioManager --> Portfolio +Portfolio --> Stock +Portfolio --> Wallet +Stock --> Transaction +Transaction --> TransactionType +} + +@enduml \ No newline at end of file diff --git a/docs/UML/ViewPortfolioState0.puml b/docs/UML/ViewPortfolioState0.puml new file mode 100644 index 0000000000..54dafa2a59 --- /dev/null +++ b/docs/UML/ViewPortfolioState0.puml @@ -0,0 +1,23 @@ +@startuml + +hide footbox +hide members +hide circle +skinparam packageStyle Rectangle +skinparam ClassFontColor #000000 +skinparam ClassBorderColor #000000 + +title Initial state + +package States { + class State1 as "__:Ui__" + class State2 as "__viewCommand:ViewCommand__" +} + +class Pointer as "Controller" #FFFFF +Pointer -down-> State1 + +class Pointer2 as "Parser" #FFFFF +Pointer2 -down-> State2 + +@enduml \ No newline at end of file diff --git a/docs/UML/ViewPortfolioState1.puml b/docs/UML/ViewPortfolioState1.puml new file mode 100644 index 0000000000..b82d69a488 --- /dev/null +++ b/docs/UML/ViewPortfolioState1.puml @@ -0,0 +1,29 @@ +@startuml + +hide footbox +hide members +hide circle +skinparam packageStyle Rectangle +skinparam ClassFontColor #000000 +skinparam ClassBorderColor #000000 + +title State after Step 2 + +package States { + class State1 as "__:Ui__" + class State2 as "__viewCommand:ViewCommand__" + class State3 as "__:PortfolioManager__" + class State4 as "__:Portfolio__" + class State5 as "__:Stock__" +} + +class Pointer as "Controller" #FFFFF +Pointer -down-> State1 +State1 -down-> State3 +State3 -down-> State4 +State4 -down-> State5 + +class Pointer2 as "Parser" #FFFFF +Pointer2 -down-> State2 + +@enduml \ No newline at end of file diff --git a/docs/UML/ViewPortfolioState2.puml b/docs/UML/ViewPortfolioState2.puml new file mode 100644 index 0000000000..a479544ede --- /dev/null +++ b/docs/UML/ViewPortfolioState2.puml @@ -0,0 +1,33 @@ +@startuml + +hide footbox +hide members +hide circle +skinparam packageStyle Rectangle +skinparam ClassFontColor #000000 +skinparam ClassBorderColor #000000 + +title State after Step 4 + +package States { + class State1 as "__:Ui__" + class State2 as "__viewCommand:ViewCommand__" + class State3 as "__:PortfolioManager__" + class State4 as "__:Portfolio__" + class State5 as "__:Stock__" + class State6 as "__:Transaction__" + class State7 as "__:StockPriceFetcher__" +} + +class Pointer as "Controller" #FFFFF +Pointer -down-> State1 +State1 -down-> State3 +State3 -down-> State4 +State4 -down-> State5 +State5 -down-> State6 +State5 -down-> State7 + +class Pointer2 as "Parser" #FFFFF +Pointer2 -down-> State2 + +@enduml \ No newline at end of file diff --git a/docs/UserGuide.md b/docs/UserGuide.md index abd9fbe891..08ad0ab9b8 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -2,41 +2,221 @@ ## Introduction -{Give a product intro} +PaperTrade is a command line trading simulator that lets you try your hand at trading stocks with no risk! -## Quick Start +__Note: Do not panic if you see the `API limit reached` error!__ + +What you are using is a free version. Hence, there is a limit of 5 API requests per minute. +You can pay for the premium version starting at only $29.99/month! -{Give steps to get started quickly} +## Quick Start 1. Ensure that you have Java 11 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +2. Down the latest version of `PaperTrade` from [here](https://github.com/AY2021S1-CS2113-T16-3/tp/releases/tag/v2.1). ## Features -{Give detailed description of each feature} +### Buying a stock: `buy /aapl quantity` +Buys the specified stock at market price + +Format: `buy /STOCK_TICKER quantity` + +* The `STOCK_TICKER` is the stock's short form +* `quantity` refers to the quantity of stocks user wants to buy; is an integer + +Example of usage and output: + +To buy 2 shares of TSLA: +``` +What would you like to do today? +buy /tsla 2 +____________________________________________________________ +You have successfully purchased 2 of tsla at 421.725. +____________________________________________________________ +____________________________________________________________ +You currently have $96049.14 in your wallet. +____________________________________________________________ +``` + +To buy 1 share of FB: +``` +What would you like to do today? +buy /fb 1 +____________________________________________________________ +You have successfully purchased 1 of fb at 282.485. +____________________________________________________________ +____________________________________________________________ +You currently have $95766.65 in your wallet. +____________________________________________________________ +``` + +### Selling a stock: `sell /aapl quantity` +Sells the specified stock at market price + +Format: `sell /STOCK_TICKER quantity` + +* The `STOCK_TICKER` is the stock's short form +* `quantity` refers to the quantity of stocks user wants to sell; is an integer + +Example of usage and output: + +To sell 1 share of GOOG: +``` +What would you like to do today? +sell /goog 1 +____________________________________________________________ +You have successfully sold 1 of goog at 1602.13. +____________________________________________________________ +____________________________________________________________ +You currently have $95766.65 in your wallet. +____________________________________________________________ +``` + +### Searching for info about a stock: `search /aapl` +Shows information about a stock like price and volume + +Format: `search /STOCK_TICKER` + +* The `STOCK_TICKER` is the stock's short form + +Example of usage: + +`search /nflx` -### Adding a todo: `todo` -Adds a new item to the list of todo items. +`search /shop` -Format: `todo n/TODO_NAME d/DEADLINE` +### View your portfolio: `view` +Shows the stocks you have, its quantity and current price. Also shows transaction history. -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +Format: `view` Example of usage: -`todo n/Write the rest of the User Guide d/next week` +``` +What would you like to do today? +view +____________________________________________________________ +1. Symbol: aapl, total quantity: 1, Current Price: 116.14500000000001 + BUY 1 at 116.14500000000001 on 2020-10-28T19:50:31.555462 +Total holding asset = $116.15 +Total cost = $116.15 +Total Profit/Loss = $0.0 + +2. Symbol: msft, total quantity: 1, Current Price: 209.695 + BUY 1 at 209.695 on 2020-10-28T19:50:36.916125 +Total holding asset = $209.7 +Total cost = $209.7 +Total Profit/Loss = $0.0 +____________________________________________________________ + +``` + +### Check your wallet: `wallet` +Shows you how much cash you have left in your wallet to buy stocks, how much you have allocated to stocks and your net profit. + +Format: `wallet` + +Example of usage: + +``` +What would you like to do today? +wallet +____________________________________________________________ +You currently have $99674.16 in your wallet. +Current market value of all your equities owned: $325.84 +Loss of : -$0.00 +____________________________________________________________ +``` + +### Bookmark stocks to keep on watchlist: `mark /STOCK_TICKER`, `unmark /STOCK_TICKER`, `bookmarks` +Allows you to bookmark stocks for easy access of stocks that you are actively watching +* The `STOCK_TICKER` is the stock's short form +* There is a limit of up to __5__ stocks that can be added to bookmarks. + +#### Marking Stocks: + +Format: `mark /STOCK_TICKER` + +Example of usage: +``` +What would you like to do today? +mark /msft +____________________________________________________________ +You have added MSFT to bookmarks. +____________________________________________________________ +``` + +#### Unmarking Stocks: + +Format: `unmark /STOCK_TICKER` + +Example of usage: +``` +What would you like to do today? +unmark /aapl +____________________________________________________________ +You have removed AAPL from bookmarks. +____________________________________________________________ +``` + +#### Viewing info of all bookmarked stocks: + +Format: `bookmarks` + +Example of usage: +``` +What would you like to do today? +bookmarks +____________________________________________________________ +Here is the latest information on AAPL: +date: 2020-11-06T20:00 +open: 118.53 +high: 118.54 +low: 118.52 +close: 118.52 +volume: 5859 +____________________________________________________________ +____________________________________________________________ +Here is the latest information on MSFT: +date: 2020-11-06T20:00 +open: 223.4 +high: 223.4 +low: 223.4 +close: 223.4 +volume: 190 +____________________________________________________________ +``` + +### Exit: `bye` +Exits the program. Duke will save your info for the next time you come back! + +Format: `bye` + +Example of usage: +``` +What would you like to do today? +bye +____________________________________________________________ +Goodbye! Hope to see you again. +____________________________________________________________ +``` -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` ## FAQ **Q**: How do I transfer my data to another computer? -**A**: {your answer here} +**A**: Just copy over any .ser files into the data folder. -## Command Summary -{Give a 'cheat sheet' of commands here} +## Command Summary -* Add todo `todo n/TODO_NAME d/DEADLINE` +* Buy a stock `buy /aapl quantity` +* Sell a stock `sell /aapl quantity` +* Search for a stock's info `search /aapl` +* View portfolio `view` +* Check your wallet `wallet` +* Bookmark a stock `mark /aapl` +* Remove a stock from bookmarks `unmark /aapl` +* View bookmarked stocks `bookmarks` +* Exit program `bye` diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000000..c741881743 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-slate \ No newline at end of file diff --git a/docs/diagrams/Architecture.png b/docs/diagrams/Architecture.png new file mode 100644 index 0000000000..d21b6f1078 Binary files /dev/null and b/docs/diagrams/Architecture.png differ diff --git a/docs/diagrams/BuyStockSequence.png b/docs/diagrams/BuyStockSequence.png new file mode 100644 index 0000000000..50b0850024 Binary files /dev/null and b/docs/diagrams/BuyStockSequence.png differ diff --git a/docs/diagrams/BuyStockState0.png b/docs/diagrams/BuyStockState0.png new file mode 100644 index 0000000000..7ac71e3420 Binary files /dev/null and b/docs/diagrams/BuyStockState0.png differ diff --git a/docs/diagrams/BuyStockState1.png b/docs/diagrams/BuyStockState1.png new file mode 100644 index 0000000000..97026d6759 Binary files /dev/null and b/docs/diagrams/BuyStockState1.png differ diff --git a/docs/diagrams/BuyStockState2.png b/docs/diagrams/BuyStockState2.png new file mode 100644 index 0000000000..9eb3212fe6 Binary files /dev/null and b/docs/diagrams/BuyStockState2.png differ diff --git a/docs/diagrams/BuyStockState3.png b/docs/diagrams/BuyStockState3.png new file mode 100644 index 0000000000..cad3b84186 Binary files /dev/null and b/docs/diagrams/BuyStockState3.png differ diff --git a/docs/diagrams/Lifecycle.png b/docs/diagrams/Lifecycle.png new file mode 100644 index 0000000000..b11c0e3bec Binary files /dev/null and b/docs/diagrams/Lifecycle.png differ diff --git a/docs/diagrams/Packages.png b/docs/diagrams/Packages.png new file mode 100644 index 0000000000..1e026aba1d Binary files /dev/null and b/docs/diagrams/Packages.png differ diff --git a/docs/diagrams/ViewPortfolioState0.png b/docs/diagrams/ViewPortfolioState0.png new file mode 100644 index 0000000000..58530761c7 Binary files /dev/null and b/docs/diagrams/ViewPortfolioState0.png differ diff --git a/docs/diagrams/ViewPortfolioState1.png b/docs/diagrams/ViewPortfolioState1.png new file mode 100644 index 0000000000..74cab56004 Binary files /dev/null and b/docs/diagrams/ViewPortfolioState1.png differ diff --git a/docs/diagrams/ViewPortfolioState2.png b/docs/diagrams/ViewPortfolioState2.png new file mode 100644 index 0000000000..77e57dfda0 Binary files /dev/null and b/docs/diagrams/ViewPortfolioState2.png differ diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index ab75b391b8..0000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,6 +0,0 @@ -# John Doe - Project Portfolio Page - -## Overview - - -### Summary of Contributions diff --git a/docs/team/junxianang.md b/docs/team/junxianang.md new file mode 100644 index 0000000000..3328548865 --- /dev/null +++ b/docs/team/junxianang.md @@ -0,0 +1,31 @@ +# Jun Xian - Project Portfolio Page + +## Overview: PaperTrade +PaperTrade is a command line paper trading application that allows users to +simulate the buying and selling of stocks. A paper trade is a simulated trade +that allows an investor to practice buying and selling stocks without risking +real money. + +### Summary of Contributions +* **New Feature**: Added **View** function + * What it does: Allow users to view portfolio of stocks that they currently own. + It includes quantity owned, transaction history, current market price and current + profit/loss. + + * Justification: This is a feature that allows users to keep track of their portfolio + of stocks and to track how well their stocks owned are doing in terms + of profit/loss, relative to latest market price. + + * Highlights: To implement this, there is `PortfolioManager` that is able to access `Portfolio` which encapsulates + `Stock` and `Transaction`. `Stock` object also has a method to retrieve latest market price through the use of + `StockPriceFetcher`. + +Code contributed: [RepoSense Link](https://nus-cs2113-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=&sort=groupTitle&sortWithin=title&since=2020-09-27&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other&tabOpen=true&tabType=authorship&zFR=false&tabAuthor=JunxianAng&tabRepo=AY2021S1-CS2113-T16-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code) + +### Documentation +* **Developer Guide**: + * Added Object State Diagrams to give the overall view of `View` portfolio stocks function. + Also explained the step by step changes made to the respective classes as the `View` method is called + in each class. + [#45](https://github.com/AY2021S1-CS2113-T16-3/tp/pull/45) + \ No newline at end of file diff --git a/docs/team/shaojing.md b/docs/team/shaojing.md new file mode 100644 index 0000000000..ced595f636 --- /dev/null +++ b/docs/team/shaojing.md @@ -0,0 +1,6 @@ +# Shao Jing - Project Portfolio Page + +## Overview: PaperTrade +PaperTrade is a command line paper trading application that allows users to simulate the buying and selling of stocks. A paper trade is a simulated trade that allows an investor to practice buying and selling stocks without risking real money. + +### Summary of Contributions diff --git a/docs/team/trolommonm.md b/docs/team/trolommonm.md new file mode 100644 index 0000000000..3e6ca9a4bf --- /dev/null +++ b/docs/team/trolommonm.md @@ -0,0 +1,64 @@ +# Alvin - Project Portfolio Page + +## Overview: PaperTrade +PaperTrade is a command line paper trading application that allows users to +simulate the buying and selling of stocks. A paper trade is a simulated trade +that allows an investor to practice buying and selling stocks without risking +real money. + +### Summary of Contributions +* **New Feature**: Added a class to call the **Alphavantage API** + * What it does: To fetch the latest prices of a particular stock given its ticker symbol. + + * Justification: This feature is important throughout the application as we need to obtain + the latest price of a stock whenever we are buying/selling a stock. Also, when we try to + view our portfolio, we need the latest price to calculate the unrealised profits etc. + + * Highlights: I used an external library ([alphavantage4j](https://github.com/patriques82/alphavantage4j)) + which is actually a Java wrapper to help simply the process of fetching the prices. I wrote a + `StockPriceFetcher` class that uses this wrapper to obtain the latest prices. + +* **New Feature**: Added **Search** function + * What it does: Return the stock information for the particular stock, given its ticker symbol. + It includes the opening price, high price, low price, closing price, etc. + + * Justification: This feature is to allow users to keep track of the latest price of a particular + stock before deciding to buy or sell. + + * Highlights: As this was one of the first feature to be implemented, I tried to lay down the + project architecture and packages, so that my other group members can easily understand contribute + their code and minimize any merge conflicts. The project is largely split into the model, controller, + api and ui, where each package would contain respective classes that each have their own responsiblities. + +* **New Feature**: Added **Buy** function + * What it does: Buy a particular stock given its ticker symbol. + + * Justification: This is one of the main feature of our program, to allow users to simulate the buying + of a stock. + + * Highlights: To implement this, I created the model classes, e.g. `Stock`, `Portfolio` and `PortfolioManager` + to abstract the features of a stock and portfolio. The `Portfolio` would hold a list of `Stock` while the + `PortfolioManager` is in charge of managing the `Portfolio`. + +* **New Feature**: Added **data persistency** + * What it does: Data persistency for all the stocks that you have purchased, so that you don't lose them + when you quit the program. + + * Justification: This is an important feature so that users can track their portfolio over time. + + * Highlights: Used `Serializable` to store the `Portfolio` object into a binary file. Loads the `Portfolio` + object from the binary file the next time the program starts again so that users don't lose their information from the + previous session. + +Code contributed: [RepoSense Link](https://nus-cs2113-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=&sort=groupTitle&sortWithin=title&since=2020-09-27&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other&tabOpen=true&tabType=authorship&zFR=false&tabAuthor=trolommonm&tabRepo=AY2021S1-CS2113-T16-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other) + +### Documentation +* **User Guide**: + * Update the User Guide to include examples of the input and output from the program. [#54](https://github.com/AY2021S1-CS2113-T16-3/tp/pull/54/commits/648043fd7c90f16c964d1d773efdd4b56e6d1939) + +* **Developer Guide**: + * Added the Package Diagram as well as the High Level Class Diagram to give the overall view of the project structure. + Also explained how to set up the project in IntelliJ, gave an overview of the responsibilities of each of the classes + and packages and a sequence diagram of the lifecycle of the program. [#35](https://github.com/AY2021S1-CS2113-T16-3/tp/pull/35) + [#94](https://github.com/AY2021S1-CS2113-T16-3/tp/pull/94) + \ No newline at end of file diff --git a/docs/team/wly99.md b/docs/team/wly99.md new file mode 100644 index 0000000000..b582a561f2 --- /dev/null +++ b/docs/team/wly99.md @@ -0,0 +1,19 @@ +# Liang Yi - Project Portfolio Page + +## Overview: PaperTrade +PaperTrade is a command line paper trading application that allows users to simulate the buying and selling of stocks. A paper trade is a simulated trade that allows an investor to practice buying and selling stocks without risking real money. + +### Summary of Contributions +* **Testing**: + * Added tests to verify that code is behaving as intended. + +### Documentation +* **User Guide**: + * Edited UG for Introduction and Quickstart. + * Added Features which include buying, selling, searching of stocks, viewing portfolio, checking your wallet and exit. + * Edited FAQ and Command Summary. + +* **Developer Guide**: + * Defined product scope along with target user profile and value proposition. + * Defined user stories and non functional requirements. + * Planned the product roadmap and project schedule. diff --git a/docs/team/yokemin.md b/docs/team/yokemin.md new file mode 100644 index 0000000000..c90c728bcf --- /dev/null +++ b/docs/team/yokemin.md @@ -0,0 +1,56 @@ +# Yoke Min - Project Portfolio Page + +## Overview: PaperTrade +PaperTrade is a command line paper trading application that allows users to +simulate the buying and selling of stocks. A paper trade is a simulated trade +that allows an investor to practice buying and selling stocks without risking +real money. + +### Summary of Contributions +* **New Feature**: Added **Sell** function + * What it does: To allow users to sell a particular stock that they currently own + + * Justification: This is one of the main feature of our program, to allow users to simulate the selling + of a stock. + + * Highlights: To implement this, I created the sellStocks methods in various classes (`Stock`, `Transaction`, + `Portfolio`, `PortfolioManager`, `Controller` and the methods to parse user inputs in the `Parser` class. + +* **New Feature**: Added **mark, unmark and bookmarks** function + * What it does: `mark` allows the user to add a stock to their bookmarks; + `unmark` allows the user to remove a stock from their bookmarks; + `bookmarks` allows the user to view the details of the stocks in their bookmarks. + + * Justification: This feature is to allow users to easily view the details of stocks that they + are interested in or want to frequently keep track of. + + * Highlights: To implement these three functions, I added the `Bookmarks` and `BookmarksManager` classes. + I also implemented command classes which helps to execute the command. The `Bookmarks` would hold an arraylist + of `Strings` which contains the ticker symbol of the bookmarked stocks. + The `BookmarkManager` is in charge of managing the `Bookmarks`. + +* **New Feature**: Added **Api** class (Not used in the end product) + * What it does: Allows us to retrieve the relevant information of the stocks + + * Justification: We needed to get real time information about the stock prices and stock details. + + * Highlights: To implement this, I used the [API documentation](https://www.alphavantage.co/documentation/) + to write the code to retrieve the JSON files that contained the information of the stocks. However, as we only needed + retrieve one type of stock data, Stock time series Intraday, we used an external library to retrieve stock + information instead. + +Code contributed: [RepoSense Link](https://nus-cs2113-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=&sort=groupTitle&sortWithin=title&since=2020-09-27&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other&tabOpen=true&tabType=authorship&zFR=false&tabAuthor=yokemin&tabRepo=AY2021S1-CS2113-T16-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other) + +### Documentation +* **User Guide**: + * Edit the User Guide for bookmarks, mark and unmark function. [#97](https://github.com/AY2021S1-CS2113-T16-3/tp/pull/97) + * Update the User Guide to correct the input formats. [#52](https://github.com/AY2021S1-CS2113-T16-3/tp/pull/52) + + +* **Developer Guide**: + * Added Object State Diagrams to give the overall view of buy/sell stock function. + Also explained the step by step changes made to the respective classes as the buyStock method is called + in each class. Added the Sequence Diagram for the flow of the sell stock function. + [#44](https://github.com/AY2021S1-CS2113-T16-3/tp/pull/44) + [#50](https://github.com/AY2021S1-CS2113-T16-3/tp/pull/50) + \ No newline at end of file diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..0881f72932 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: seedu.duke.PaperTrade + diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java deleted file mode 100644 index 5c74e68d59..0000000000 --- a/src/main/java/seedu/duke/Duke.java +++ /dev/null @@ -1,21 +0,0 @@ -package seedu.duke; - -import java.util.Scanner; - -public class Duke { - /** - * Main entry-point for the java.duke.Duke application. - */ - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - System.out.println("What is your name?"); - - Scanner in = new Scanner(System.in); - System.out.println("Hello " + in.nextLine()); - } -} diff --git a/src/main/java/seedu/duke/PaperTrade.java b/src/main/java/seedu/duke/PaperTrade.java new file mode 100644 index 0000000000..920fef2a63 --- /dev/null +++ b/src/main/java/seedu/duke/PaperTrade.java @@ -0,0 +1,15 @@ +package seedu.duke; + +import seedu.duke.controller.Controller; + +public class PaperTrade { + + /** + * Main entry-point for the java.duke.Duke application. + */ + public static void main(String[] args) { + Controller c = new Controller(); + c.runApp(); + } + +} diff --git a/src/main/java/seedu/duke/api/Api.java b/src/main/java/seedu/duke/api/Api.java new file mode 100644 index 0000000000..1c1330e616 --- /dev/null +++ b/src/main/java/seedu/duke/api/Api.java @@ -0,0 +1,64 @@ +package seedu.duke.api; + + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Map; + +public class Api { + + private static final String API_KEY = "O8EVQ7YSWDW08BN9"; + public static final String SYMBOL = "IBM"; + + + public Api() throws IOException { + + StringBuilder request = new StringBuilder(); + request.append("https://www.alphavantage.co/query?"); + + Map parameters = new HashMap<>(); + parameters.put("symbol", SYMBOL); + parameters.put("function", "TIME_SERIES_DAILY_ADJUSTED"); + + String paramsToAppend = buildParamsString(parameters); + + request = request.append(paramsToAppend); + + // Creating a Request + URL url = new URL(request.toString()); + URLConnection con = url.openConnection(); + HttpURLConnection http = (HttpURLConnection) con; + http.setRequestMethod("GET"); + + // Prints response from API call + String response = FullResponseBuilder.getFullResponse(http); + // System.out.println(response); + + } + + // Builds a string to append relevant the params to append to URL + public static String buildParamsString(Map params) + throws UnsupportedEncodingException { + StringBuilder result = new StringBuilder(); + + for (Map.Entry entry : params.entrySet()) { + result.append(URLEncoder.encode(entry.getKey(), "UTF-8")); + result.append("="); + result.append(URLEncoder.encode(entry.getValue(), "UTF-8")); + result.append("&"); + } + + result.append("apikey=" + API_KEY); + + String resultString = result.toString(); + return resultString.length() > 0 + ? resultString.substring(0, resultString.length() - 1) + : resultString; + } + +} diff --git a/src/main/java/seedu/duke/api/FullResponseBuilder.java b/src/main/java/seedu/duke/api/FullResponseBuilder.java new file mode 100644 index 0000000000..89d4dde772 --- /dev/null +++ b/src/main/java/seedu/duke/api/FullResponseBuilder.java @@ -0,0 +1,35 @@ +package seedu.duke.api; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.HttpURLConnection; + +public class FullResponseBuilder { + public static String getFullResponse(HttpURLConnection con) throws IOException { + + Reader streamReader = null; + if (con.getResponseCode() > 299) { + streamReader = new InputStreamReader(con.getErrorStream()); + } else { + streamReader = new InputStreamReader(con.getInputStream(), "utf-8"); + } + + BufferedReader br = new BufferedReader(streamReader); + String responseLine = null; + StringBuilder content = new StringBuilder(); + while ((responseLine = br.readLine()) != null) { + content.append(responseLine); + } + + br.close(); + + StringBuilder fullResponseBuilder = new StringBuilder(); + + fullResponseBuilder.append("Response: ") + .append(content); + + return fullResponseBuilder.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/seedu/duke/api/StockPriceFetcher.java b/src/main/java/seedu/duke/api/StockPriceFetcher.java new file mode 100644 index 0000000000..e6c70e9363 --- /dev/null +++ b/src/main/java/seedu/duke/api/StockPriceFetcher.java @@ -0,0 +1,54 @@ +package seedu.duke.api; + +import org.patriques.AlphaVantageConnector; +import org.patriques.TimeSeries; +import org.patriques.input.timeseries.Interval; +import org.patriques.input.timeseries.OutputSize; +import org.patriques.output.AlphaVantageException; +import org.patriques.output.timeseries.IntraDay; +import org.patriques.output.timeseries.data.StockData; +import seedu.duke.exception.PaperTradeException; + +import java.util.List; +import java.util.Random; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class StockPriceFetcher { + private static Logger logger = Logger.getLogger("tp"); + private String[] apiKeys = {"O8EVQ7YSWDW08BN9", "3FPNCQ0JNYEE8BTK", "072ESZXDIDEATZA2", "Q3JB9M20HYCUXBFR", + "K0O2GKDS6Q14H72Q", "9N7N2SEJ1XB2MYXH", "MPGB17OYPB9T9UV4", "V1E553AD7IPL60L7", + "XLCF6EOMHCE76FMG", "SXRMWI9LMKTUGMYR", "UDK2VLO64K1Z8UA2", "8X57YW7EXHPZ66J1"}; + private int timeout = 3000; + + public StockPriceFetcher() { + timeout = 3000; + } + + public double fetchLatestPrice(String symbol) throws PaperTradeException { + logger.setLevel(Level.WARNING); + logger.log(Level.INFO, "fetching latest stock data for " + symbol); + StockData stockData = fetchLatestStockData(symbol); + + return (stockData.getHigh() + stockData.getLow()) / 2; + } + + public StockData fetchLatestStockData(String symbol) throws PaperTradeException { + Random random = new Random(); + String randomApiKey = apiKeys[random.nextInt(apiKeys.length)]; + AlphaVantageConnector apiConnector = new AlphaVantageConnector(randomApiKey, timeout); + TimeSeries stockTimeSeries = new TimeSeries(apiConnector); + + try { + IntraDay response = stockTimeSeries.intraDay(symbol, Interval.ONE_MIN, OutputSize.COMPACT); + List stockData = response.getStockData(); + + return stockData.get(0); + } catch (AlphaVantageException e) { + logger.setLevel(Level.WARNING); + logger.log(Level.INFO, "failed to fetch price from API"); + throw new PaperTradeException("API limit reached. Take a chill pill and try again a minute later :)"); + } + } + +} diff --git a/src/main/java/seedu/duke/command/AddBookmarkCommand.java b/src/main/java/seedu/duke/command/AddBookmarkCommand.java new file mode 100644 index 0000000000..8cb5599253 --- /dev/null +++ b/src/main/java/seedu/duke/command/AddBookmarkCommand.java @@ -0,0 +1,14 @@ +package seedu.duke.command; + +public class AddBookmarkCommand extends Command { + + private String symbol; + + public AddBookmarkCommand(String symbol) { + this.symbol = symbol; + } + + public String getSymbol() { + return symbol; + } +} diff --git a/src/main/java/seedu/duke/command/BuyCommand.java b/src/main/java/seedu/duke/command/BuyCommand.java new file mode 100644 index 0000000000..73df125b60 --- /dev/null +++ b/src/main/java/seedu/duke/command/BuyCommand.java @@ -0,0 +1,19 @@ +package seedu.duke.command; + +public class BuyCommand extends Command { + private String symbol; + private int quantity; + + public BuyCommand(String symbol, int quantity) { + this.symbol = symbol; + this.quantity = quantity; + } + + public String getSymbol() { + return symbol; + } + + public int getQuantity() { + return quantity; + } +} diff --git a/src/main/java/seedu/duke/command/ByeCommand.java b/src/main/java/seedu/duke/command/ByeCommand.java new file mode 100644 index 0000000000..b9e5bd8b2b --- /dev/null +++ b/src/main/java/seedu/duke/command/ByeCommand.java @@ -0,0 +1,5 @@ +package seedu.duke.command; + +public class ByeCommand extends Command { + +} diff --git a/src/main/java/seedu/duke/command/Command.java b/src/main/java/seedu/duke/command/Command.java new file mode 100644 index 0000000000..579c997a31 --- /dev/null +++ b/src/main/java/seedu/duke/command/Command.java @@ -0,0 +1,5 @@ +package seedu.duke.command; + +public abstract class Command { + +} diff --git a/src/main/java/seedu/duke/command/InvalidCommand.java b/src/main/java/seedu/duke/command/InvalidCommand.java new file mode 100644 index 0000000000..d3e865c55c --- /dev/null +++ b/src/main/java/seedu/duke/command/InvalidCommand.java @@ -0,0 +1,13 @@ +package seedu.duke.command; + +public class InvalidCommand extends Command { + String errorMessage; + + public InvalidCommand(String errorMessage) { + this.errorMessage = errorMessage; + } + + public String getErrorMessage() { + return errorMessage; + } +} diff --git a/src/main/java/seedu/duke/command/RemoveBookmarkCommand.java b/src/main/java/seedu/duke/command/RemoveBookmarkCommand.java new file mode 100644 index 0000000000..36978a71d5 --- /dev/null +++ b/src/main/java/seedu/duke/command/RemoveBookmarkCommand.java @@ -0,0 +1,14 @@ +package seedu.duke.command; + +public class RemoveBookmarkCommand extends Command { + + private String symbol; + + public RemoveBookmarkCommand(String symbol) { + this.symbol = symbol; + } + + public String getSymbol() { + return symbol; + } +} diff --git a/src/main/java/seedu/duke/command/SearchCommand.java b/src/main/java/seedu/duke/command/SearchCommand.java new file mode 100644 index 0000000000..e7dcec05f9 --- /dev/null +++ b/src/main/java/seedu/duke/command/SearchCommand.java @@ -0,0 +1,15 @@ +package seedu.duke.command; + +public class SearchCommand extends Command { + + private String searchKey; + + public SearchCommand(String searchKey) { + this.searchKey = searchKey; + } + + public String getSearchKey() { + return searchKey; + } + +} diff --git a/src/main/java/seedu/duke/command/SellCommand.java b/src/main/java/seedu/duke/command/SellCommand.java new file mode 100644 index 0000000000..2d5350ad9e --- /dev/null +++ b/src/main/java/seedu/duke/command/SellCommand.java @@ -0,0 +1,19 @@ +package seedu.duke.command; + +public class SellCommand extends Command { + private String symbol; + private int quantity; + + public SellCommand(String symbol, int quantity) { + this.symbol = symbol; + this.quantity = quantity; + } + + public String getSymbol() { + return symbol; + } + + public int getQuantity() { + return quantity; + } +} diff --git a/src/main/java/seedu/duke/command/ViewBookmarkedStocksCommand.java b/src/main/java/seedu/duke/command/ViewBookmarkedStocksCommand.java new file mode 100644 index 0000000000..02be8fd6ba --- /dev/null +++ b/src/main/java/seedu/duke/command/ViewBookmarkedStocksCommand.java @@ -0,0 +1,5 @@ +package seedu.duke.command; + +public class ViewBookmarkedStocksCommand extends Command { + +} diff --git a/src/main/java/seedu/duke/command/ViewCommand.java b/src/main/java/seedu/duke/command/ViewCommand.java new file mode 100644 index 0000000000..b12f50bd64 --- /dev/null +++ b/src/main/java/seedu/duke/command/ViewCommand.java @@ -0,0 +1,5 @@ +package seedu.duke.command; + +public class ViewCommand extends Command { + +} diff --git a/src/main/java/seedu/duke/command/WalletCommand.java b/src/main/java/seedu/duke/command/WalletCommand.java new file mode 100644 index 0000000000..fc8ef67501 --- /dev/null +++ b/src/main/java/seedu/duke/command/WalletCommand.java @@ -0,0 +1,4 @@ +package seedu.duke.command; + +public class WalletCommand extends Command { +} diff --git a/src/main/java/seedu/duke/controller/Controller.java b/src/main/java/seedu/duke/controller/Controller.java new file mode 100644 index 0000000000..6075edea9a --- /dev/null +++ b/src/main/java/seedu/duke/controller/Controller.java @@ -0,0 +1,180 @@ +package seedu.duke.controller; + +import org.patriques.output.timeseries.data.StockData; +import seedu.duke.api.StockPriceFetcher; +import seedu.duke.command.AddBookmarkCommand; +import seedu.duke.command.BuyCommand; +import seedu.duke.command.ByeCommand; +import seedu.duke.command.Command; +import seedu.duke.command.InvalidCommand; +import seedu.duke.command.RemoveBookmarkCommand; +import seedu.duke.command.SearchCommand; +import seedu.duke.command.SellCommand; +import seedu.duke.command.ViewBookmarkedStocksCommand; +import seedu.duke.command.ViewCommand; +import seedu.duke.command.WalletCommand; +import seedu.duke.exception.DoNotOwnStockException; +import seedu.duke.exception.InsufficientFundException; +import seedu.duke.exception.InsufficientQtyException; +import seedu.duke.exception.NegativeQtyException; +import seedu.duke.exception.PaperTradeException; +import seedu.duke.model.BookmarksManager; +import seedu.duke.model.PortfolioManager; +import seedu.duke.model.Stock; +import seedu.duke.parser.Parser; +import seedu.duke.ui.Ui; + +public class Controller { + private Ui ui; + private StockPriceFetcher stockPriceFetcher; + private PortfolioManager portfolioManager; + private BookmarksManager bookmarksManager; + + public Controller() { + ui = new Ui(); + stockPriceFetcher = new StockPriceFetcher(); + portfolioManager = new PortfolioManager(); + bookmarksManager = new BookmarksManager(); + } + + private void buyStock(String symbol, int quantity) { + try { + double price = stockPriceFetcher.fetchLatestPrice(symbol); + portfolioManager.buyStock(symbol, quantity, price); + + ui.printWithDivider("You have successfully purchased " + + quantity + " of " + symbol + " at " + price + "."); + ui.printWithDivider("You currently have $" + + String.format("%.02f", portfolioManager.getWalletCurrentAmount()) + " in your wallet."); + } catch (PaperTradeException | InsufficientFundException | NegativeQtyException e) { + ui.printWithDivider(e.getMessage()); + } + } + + private void sellStock(String symbol, int quantity) { + try { + double price = stockPriceFetcher.fetchLatestPrice(symbol); + portfolioManager.sellStock(symbol, quantity, price); + + ui.printWithDivider("You have successfully sold " + + quantity + " of " + symbol + " at " + price + "."); + ui.printWithDivider("You currently have $" + + String.format("%.02f", portfolioManager.getWalletCurrentAmount()) + " in your wallet."); + } catch (DoNotOwnStockException | InsufficientQtyException | PaperTradeException | NegativeQtyException e) { + ui.printWithDivider(e.getMessage()); + } + } + + public void runApp() { + ui.greetUser(); + + while (true) { + String userInput = ui.getUserInput(); + Command command = Parser.parseCommand(userInput); + if (!executeCommand(command)) { + break; + } + } + } + + public void addBookmark(String symbol) { + try { + stockPriceFetcher.fetchLatestPrice(symbol); + } catch (PaperTradeException e) { + ui.printWithDivider("Error validating stock, might be an invalid stock!"); + return; + } + try { + bookmarksManager.addToBookmarks(symbol); + ui.printWithDivider("You have added " + symbol + " to bookmarks."); + } catch (PaperTradeException e) { + ui.printWithDivider(e.getMessage()); + } + } + + public void removeBookmark(String symbol) { + try { + bookmarksManager.removeBookmark(symbol); + ui.printWithDivider("You have removed " + symbol + " from bookmarks."); + } catch (PaperTradeException e) { + ui.printWithDivider(e.getMessage()); + } + } + + private boolean executeCommand(Command command) { + if (command instanceof SearchCommand) { + SearchCommand searchCommand = (SearchCommand) command; + searchSymbol(searchCommand.getSearchKey()); + } else if (command instanceof InvalidCommand) { + InvalidCommand invalidCommand = (InvalidCommand) command; + ui.printWithDivider(invalidCommand.getErrorMessage()); + } else if (command instanceof ByeCommand) { + ui.printWithDivider("Goodbye! Hope to see you again."); + return false; + } else if (command instanceof BuyCommand) { + BuyCommand buyCommand = (BuyCommand) command; + buyStock(buyCommand.getSymbol(), buyCommand.getQuantity()); + } else if (command instanceof SellCommand) { + SellCommand sellCommand = (SellCommand) command; + sellStock(sellCommand.getSymbol(), sellCommand.getQuantity()); + } else if (command instanceof ViewCommand) { + viewPortfolio(); + } else if (command instanceof WalletCommand) { + viewWallet(); + } else if (command instanceof AddBookmarkCommand) { + AddBookmarkCommand addBookmarkCommand = (AddBookmarkCommand) command; + addBookmark(addBookmarkCommand.getSymbol()); + } else if (command instanceof RemoveBookmarkCommand) { + RemoveBookmarkCommand removeBookmarkCommand = (RemoveBookmarkCommand) command; + removeBookmark(removeBookmarkCommand.getSymbol()); + } else if (command instanceof ViewBookmarkedStocksCommand) { + viewBookmarkedStocks(); + } + + return true; + } + + private void viewBookmarkedStocks() { + if (bookmarksManager.getBookmarks().getBookmarkedStocks().size() == 0) { + ui.printWithDivider("Currently no stocks bookmarked! Try bookmarking stock using mark /TICKER"); + } + for (String symbol : bookmarksManager.getBookmarks().getBookmarkedStocks()) { + searchSymbol(symbol); + } + } + + public void viewPortfolio() { + ui.view(portfolioManager.getAllStocks()); + } + + public void viewWallet() { + double amount = 0.00; + for (Stock stock : portfolioManager.getAllStocks()) { + try { + amount += (stockPriceFetcher.fetchLatestPrice(stock.getSymbol())) * stock.getTotalQuantity(); + } catch (PaperTradeException e) { + ui.printWithDivider(e.getMessage()); + } + } + ui.viewWallet(portfolioManager.getWalletCurrentAmount(), portfolioManager.getWalletInitialAmount(), amount); + } + + public void searchSymbol(String symbol) { + try { + StockData stockData = stockPriceFetcher.fetchLatestStockData(symbol); + + ui.printWithDivider( + "Here is the latest information on " + symbol + ":", + "date: " + stockData.getDateTime(), + "open: " + stockData.getOpen(), + "high: " + stockData.getHigh(), + "low: " + stockData.getLow(), + "close: " + stockData.getClose(), + "volume: " + stockData.getVolume() + ); + } catch (PaperTradeException e) { + ui.printWithDivider(e.getMessage()); + } + } + +} diff --git a/src/main/java/seedu/duke/exception/DoNotOwnStockException.java b/src/main/java/seedu/duke/exception/DoNotOwnStockException.java new file mode 100644 index 0000000000..4ffdce062e --- /dev/null +++ b/src/main/java/seedu/duke/exception/DoNotOwnStockException.java @@ -0,0 +1,7 @@ +package seedu.duke.exception; + +public class DoNotOwnStockException extends Exception { + public DoNotOwnStockException() { + super("You do not own this stock!"); + } +} diff --git a/src/main/java/seedu/duke/exception/InsufficientFundException.java b/src/main/java/seedu/duke/exception/InsufficientFundException.java new file mode 100644 index 0000000000..efe42b2c0e --- /dev/null +++ b/src/main/java/seedu/duke/exception/InsufficientFundException.java @@ -0,0 +1,7 @@ +package seedu.duke.exception; + +public class InsufficientFundException extends Exception { + public InsufficientFundException() { + super("You have insufficient fund in your wallet. Please try again."); + } +} diff --git a/src/main/java/seedu/duke/exception/InsufficientQtyException.java b/src/main/java/seedu/duke/exception/InsufficientQtyException.java new file mode 100644 index 0000000000..6456593b9a --- /dev/null +++ b/src/main/java/seedu/duke/exception/InsufficientQtyException.java @@ -0,0 +1,7 @@ +package seedu.duke.exception; + +public class InsufficientQtyException extends Exception { + public InsufficientQtyException(int quantityOwned) { + super("You only own " + quantityOwned + " of this stock!"); + } +} diff --git a/src/main/java/seedu/duke/exception/NegativeQtyException.java b/src/main/java/seedu/duke/exception/NegativeQtyException.java new file mode 100644 index 0000000000..8f7046ac24 --- /dev/null +++ b/src/main/java/seedu/duke/exception/NegativeQtyException.java @@ -0,0 +1,7 @@ +package seedu.duke.exception; + +public class NegativeQtyException extends Exception { + public NegativeQtyException() { + super("You cannot buy/sell zero or negative quantity!"); + } +} diff --git a/src/main/java/seedu/duke/exception/PaperTradeException.java b/src/main/java/seedu/duke/exception/PaperTradeException.java new file mode 100644 index 0000000000..10cb641d9c --- /dev/null +++ b/src/main/java/seedu/duke/exception/PaperTradeException.java @@ -0,0 +1,9 @@ +package seedu.duke.exception; + +public class PaperTradeException extends Exception { + + public PaperTradeException(String errorMessage) { + super(errorMessage); + } + +} diff --git a/src/main/java/seedu/duke/model/Bookmarks.java b/src/main/java/seedu/duke/model/Bookmarks.java new file mode 100644 index 0000000000..109e9904e7 --- /dev/null +++ b/src/main/java/seedu/duke/model/Bookmarks.java @@ -0,0 +1,41 @@ +package seedu.duke.model; + +import seedu.duke.exception.PaperTradeException; + +import java.io.Serializable; +import java.util.ArrayList; + +public class Bookmarks implements Serializable { + + private ArrayList bookmarkedStocks; + + public Bookmarks() { + bookmarkedStocks = new ArrayList<>(); + } + + public Bookmarks(ArrayList bookmarkedStocks) { + this.bookmarkedStocks = bookmarkedStocks; + } + + public ArrayList getBookmarkedStocks() { + return bookmarkedStocks; + } + + public void addToBookmarks(String symbol) throws PaperTradeException { + if (bookmarkedStocks.size() == 5) { + throw new PaperTradeException("Maximum of 5 bookmarked stocks reached!"); + } + if (bookmarkedStocks.contains(symbol)) { + throw new PaperTradeException("Already bookmarked this stock!"); + } + bookmarkedStocks.add(symbol); + } + + public void removeBookmark(String symbol) throws PaperTradeException { + if (!bookmarkedStocks.contains(symbol)) { + throw new PaperTradeException("This stock is not bookmarked!"); + } + bookmarkedStocks.remove(symbol); + } + +} diff --git a/src/main/java/seedu/duke/model/BookmarksManager.java b/src/main/java/seedu/duke/model/BookmarksManager.java new file mode 100644 index 0000000000..71abc42fdc --- /dev/null +++ b/src/main/java/seedu/duke/model/BookmarksManager.java @@ -0,0 +1,65 @@ +package seedu.duke.model; + +import seedu.duke.exception.PaperTradeException; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InvalidClassException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +public class BookmarksManager { + + private Bookmarks bookmarks; + + public BookmarksManager() { + load(); + } + + public Bookmarks getBookmarks() { + return bookmarks; + } + + public void addToBookmarks(String symbol) throws PaperTradeException { + bookmarks.addToBookmarks(symbol); + save(); + } + + public void removeBookmark(String symbol) throws PaperTradeException { + bookmarks.removeBookmark(symbol); + save(); + } + + private void load() { + try { + FileInputStream fis = new FileInputStream("data/bookmarks.ser"); + ObjectInputStream ois = new ObjectInputStream(fis); + bookmarks = (Bookmarks) ois.readObject(); + ois.close(); + fis.close(); + } catch (FileNotFoundException e) { + bookmarks = new Bookmarks(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (InvalidClassException e) { + bookmarks = new Bookmarks(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void save() { + try { + FileOutputStream fos = new FileOutputStream("data/bookmarks.ser"); + ObjectOutputStream oos = new ObjectOutputStream(fos); + oos.writeObject(bookmarks); + oos.close(); + fos.close(); + } catch (IOException e) { + System.out.println(e); + } + } + +} diff --git a/src/main/java/seedu/duke/model/Portfolio.java b/src/main/java/seedu/duke/model/Portfolio.java new file mode 100644 index 0000000000..bcf7a87901 --- /dev/null +++ b/src/main/java/seedu/duke/model/Portfolio.java @@ -0,0 +1,88 @@ +package seedu.duke.model; + +import seedu.duke.exception.DoNotOwnStockException; +import seedu.duke.exception.InsufficientFundException; +import seedu.duke.exception.InsufficientQtyException; +import seedu.duke.exception.NegativeQtyException; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; + +public class Portfolio implements Serializable { + HashMap stocks; + Wallet wallet; + + public Portfolio() { + stocks = new HashMap<>(); + wallet = new Wallet(100000); + assert getWalletInitialAmount() <= 100000; + } + + public void buyStock(String symbol, int quantity, double buyPrice) + throws InsufficientFundException, NegativeQtyException { + assert buyPrice > 0 : "buyPrice should be more than 0"; + + if (quantity <= 0) { + throw new NegativeQtyException(); + } + + if (wallet.getCurrentAmount() < buyPrice * quantity) { + throw new InsufficientFundException(); + } + + wallet.buyStock(quantity, buyPrice); + Transaction transaction = new Transaction(TransactionType.BUY, quantity, buyPrice, LocalDateTime.now()); + if (stocks.get(symbol) == null) { + Stock stock = new Stock(symbol); + stock.addTransaction(transaction); + stocks.put(symbol, stock); + } else { + Stock stock = stocks.get(symbol); + stock.addTransaction(transaction); + } + } + + public void sellStock(String symbol, int quantity, double sellPrice) + throws InsufficientQtyException, DoNotOwnStockException, NegativeQtyException { + if (stocks.get(symbol) == null) { + throw new DoNotOwnStockException(); + } else if (stocks.get(symbol).getTotalQuantity() < quantity) { + throw new InsufficientQtyException(stocks.get(symbol).getTotalQuantity()); + } + + if (quantity < 0) { + throw new NegativeQtyException(); + } + + assert sellPrice > 0 : "sellPrice should be more than 0"; + + wallet.sellStock(quantity, sellPrice); + Transaction transaction = new Transaction(TransactionType.SELL, quantity, sellPrice, LocalDateTime.now()); + Stock stock = stocks.get(symbol); + stock.addTransaction(transaction); + + if (stock.getTotalQuantity() == 0) { + stocks.remove(symbol); + } + } + + public double getWalletCurrentAmount() { + return wallet.getCurrentAmount(); + } + + public double getWalletInitialAmount() { + return wallet.getInitialAmount(); + } + + public ArrayList getAllStocks() { + ArrayList stocksArrayList = new ArrayList<>(); + for (Stock stock : stocks.values()) { + stocksArrayList.add(stock); + } + + return stocksArrayList; + } + +} diff --git a/src/main/java/seedu/duke/model/PortfolioManager.java b/src/main/java/seedu/duke/model/PortfolioManager.java new file mode 100644 index 0000000000..7467a77dfd --- /dev/null +++ b/src/main/java/seedu/duke/model/PortfolioManager.java @@ -0,0 +1,91 @@ +package seedu.duke.model; + + +import seedu.duke.exception.DoNotOwnStockException; +import seedu.duke.exception.InsufficientFundException; +import seedu.duke.exception.InsufficientQtyException; +import seedu.duke.exception.NegativeQtyException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InvalidClassException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class PortfolioManager { + private Portfolio portfolio; + private static Logger logger = Logger.getLogger("tp"); + + public PortfolioManager() { + File directory = new File("data"); + if (!directory.exists()) { + directory.mkdir(); + } + load(); + } + + public void buyStock(String symbol, int quantity, double buyPrice) + throws InsufficientFundException, NegativeQtyException { + logger.setLevel(Level.WARNING); + logger.log(Level.INFO, "buying stock ..."); + portfolio.buyStock(symbol, quantity, buyPrice); + save(); + } + + public void sellStock(String symbol, int quantity, double sellPrice) + throws InsufficientQtyException, DoNotOwnStockException, NegativeQtyException { + logger.setLevel(Level.WARNING); + logger.log(Level.INFO, "selling stock ..."); + portfolio.sellStock(symbol, quantity, sellPrice); + save(); + } + + public double getWalletCurrentAmount() { + return portfolio.getWalletCurrentAmount(); + } + + public double getWalletInitialAmount() { + return portfolio.getWalletInitialAmount(); + } + + public ArrayList getAllStocks() { + return portfolio.getAllStocks(); + } + + private void load() { + try { + FileInputStream fis = new FileInputStream("data/portfolio.ser"); + ObjectInputStream ois = new ObjectInputStream(fis); + portfolio = (Portfolio) ois.readObject(); + ois.close(); + fis.close(); + } catch (FileNotFoundException e) { + portfolio = new Portfolio(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (InvalidClassException e) { + portfolio = new Portfolio(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void save() { + try { + FileOutputStream fos = new FileOutputStream("data/portfolio.ser"); + ObjectOutputStream oos = new ObjectOutputStream(fos); + oos.writeObject(portfolio); + oos.close(); + fos.close(); + } catch (IOException e) { + System.out.println(e); + } + } + +} diff --git a/src/main/java/seedu/duke/model/Stock.java b/src/main/java/seedu/duke/model/Stock.java new file mode 100644 index 0000000000..efbff07444 --- /dev/null +++ b/src/main/java/seedu/duke/model/Stock.java @@ -0,0 +1,66 @@ +package seedu.duke.model; + +import seedu.duke.api.StockPriceFetcher; +import seedu.duke.exception.PaperTradeException; +import seedu.duke.ui.Ui; + +import java.io.Serializable; +import java.util.ArrayList; + +public class Stock implements Serializable { + private String symbol; + private int totalQuantity; + private ArrayList transactions; + + public Stock(String symbol) { + this.symbol = symbol; + transactions = new ArrayList<>(); + } + + public void addTransaction(Transaction transaction) { + assert transactions != null : "ArrayList of transactions not initialised!"; + + switch (transaction.getTransactionType()) { + case BUY: + totalQuantity += transaction.getQuantity(); + break; + case SELL: + totalQuantity -= transaction.getQuantity(); + break; + default: + assert false : "How did you end up here?!"; + } + + transactions.add(transaction); + } + + public ArrayList getTransactions() { + return transactions; + } + + public String getSymbol() { + return symbol; + } + + public int getTotalQuantity() { + return totalQuantity; + } + + public double getLatestPrice() { + StockPriceFetcher stockPriceFetcher = new StockPriceFetcher(); + Ui ui = new Ui(); + try { + double price = stockPriceFetcher.fetchLatestPrice(getSymbol()); + return price; + } catch (PaperTradeException e) { + ui.print(e.getMessage()); + return 0; + } + } + + @Override + public String toString() { + return "Symbol: " + getSymbol() + ", total quantity: " + getTotalQuantity() + ", Current Price: " + + getLatestPrice(); + } +} diff --git a/src/main/java/seedu/duke/model/Transaction.java b/src/main/java/seedu/duke/model/Transaction.java new file mode 100644 index 0000000000..c27f5c2bec --- /dev/null +++ b/src/main/java/seedu/duke/model/Transaction.java @@ -0,0 +1,35 @@ +package seedu.duke.model; + +import java.io.Serializable; +import java.time.LocalDateTime; + +public class Transaction implements Serializable { + private TransactionType transactionType; + private int quantity; + private double unitPrice; + LocalDateTime dateTimeOfTransaction; + + public Transaction(TransactionType transType, int quantity, double unitPrice, LocalDateTime dateTime) { + this.transactionType = transType; + this.quantity = quantity; + this.unitPrice = unitPrice; + this.dateTimeOfTransaction = dateTime; + } + + public int getQuantity() { + return quantity; + } + + public double getUnitPrice() { + return unitPrice; + } + + public TransactionType getTransactionType() { + return transactionType; + } + + @Override + public String toString() { + return transactionType + " " + quantity + " at " + unitPrice + " on " + dateTimeOfTransaction; + } +} diff --git a/src/main/java/seedu/duke/model/TransactionType.java b/src/main/java/seedu/duke/model/TransactionType.java new file mode 100644 index 0000000000..0da9e348ff --- /dev/null +++ b/src/main/java/seedu/duke/model/TransactionType.java @@ -0,0 +1,6 @@ +package seedu.duke.model; + +public enum TransactionType { + BUY, + SELL +} diff --git a/src/main/java/seedu/duke/model/Wallet.java b/src/main/java/seedu/duke/model/Wallet.java new file mode 100644 index 0000000000..ed48a75e64 --- /dev/null +++ b/src/main/java/seedu/duke/model/Wallet.java @@ -0,0 +1,29 @@ +package seedu.duke.model; + +import java.io.Serializable; + +public class Wallet implements Serializable { + private double currentAmount; + private double initialAmount; + + public Wallet(double initialAmount) { + this.initialAmount = initialAmount; + currentAmount = initialAmount; + } + + public double getCurrentAmount() { + return currentAmount; + } + + public double getInitialAmount() { + return initialAmount; + } + + public void sellStock(int quantity, double price) { + currentAmount = currentAmount + price * quantity; + } + + public void buyStock(int quantity, double price) { + currentAmount = currentAmount - (price * quantity); + } +} diff --git a/src/main/java/seedu/duke/parser/Parser.java b/src/main/java/seedu/duke/parser/Parser.java new file mode 100644 index 0000000000..dc3bcfdac9 --- /dev/null +++ b/src/main/java/seedu/duke/parser/Parser.java @@ -0,0 +1,163 @@ +package seedu.duke.parser; + +import seedu.duke.command.AddBookmarkCommand; +import seedu.duke.command.BuyCommand; +import seedu.duke.command.ByeCommand; +import seedu.duke.command.Command; +import seedu.duke.command.InvalidCommand; +import seedu.duke.command.RemoveBookmarkCommand; +import seedu.duke.command.SearchCommand; +import seedu.duke.command.SellCommand; +import seedu.duke.command.ViewBookmarkedStocksCommand; +import seedu.duke.command.ViewCommand; +import seedu.duke.command.WalletCommand; +import seedu.duke.exception.PaperTradeException; + +import java.util.logging.Level; +import java.util.logging.Logger; + + +public class Parser { + private static Logger logger = Logger.getLogger("tp"); + + public static Command parseCommand(String userInput) { + logger.setLevel(Level.WARNING); + logger.log(Level.INFO, "going to start processing command"); + + String[] userInputSplit = userInput.trim().split(" "); + String commandString = userInputSplit[0].toLowerCase(); + + switch (commandString) { + case "search": + try { + return parseSearch(userInputSplit); + } catch (PaperTradeException e) { + return new InvalidCommand(e.getMessage()); + } + case "buy": + try { + return parseBuy(userInputSplit); + } catch (PaperTradeException e) { + return new InvalidCommand(e.getMessage()); + } + case "sell": + try { + return parseSell(userInputSplit); + } catch (PaperTradeException e) { + return new InvalidCommand(e.getMessage()); + } + case "bye": + return new ByeCommand(); + case "view": + return new ViewCommand(); + case "wallet": + return new WalletCommand(); + case "mark": + try { + return parseMark(userInputSplit); + } catch (PaperTradeException e) { + return new InvalidCommand(e.getMessage()); + } + case "unmark": + try { + return parseUnmark(userInputSplit); + } catch (PaperTradeException e) { + return new InvalidCommand(e.getMessage()); + } + case "bookmarks": + try { + return parseBookmarks(userInputSplit); + } catch (PaperTradeException e) { + return new InvalidCommand(e.getMessage()); + } + default: + logger.setLevel(Level.WARNING); + logger.log(Level.WARNING, "processing error when parsing command"); + return new InvalidCommand("Invalid command! Please try again."); + } + } + + public static Command parseBookmarks(String[] userInputSplit) throws PaperTradeException { + if (userInputSplit.length > 1) { + throw new PaperTradeException("There should not be an argument behind bookmarks!"); + } + return new ViewBookmarkedStocksCommand(); + } + + public static Command parseMark(String[] userInputSplit) throws PaperTradeException { + try { + if (!userInputSplit[1].startsWith("/")) { + throw new PaperTradeException("Please enter the ticker symbol of the company that you want to mark!"); + } + String symbol = userInputSplit[1].substring(1).toUpperCase(); + return new AddBookmarkCommand(symbol); + } catch (IndexOutOfBoundsException e) { + throw new PaperTradeException(("Wrong input format! E.g. mark /MSFT")); + } + } + + public static Command parseUnmark(String[] userInputSplit) throws PaperTradeException { + try { + if (!userInputSplit[1].startsWith("/")) { + throw new PaperTradeException("Please enter the ticker symbol of the company that you want to unmark!"); + } + String symbol = userInputSplit[1].substring(1).toUpperCase(); + return new RemoveBookmarkCommand(symbol); + } catch (IndexOutOfBoundsException e) { + throw new PaperTradeException(("Wrong input format! E.g. unmark /MSFT")); + } + } + + public static Command parseBuy(String[] userInputSplit) throws PaperTradeException { + try { + if (!userInputSplit[1].startsWith("/")) { + throw new PaperTradeException("Please enter the ticker symbol of the company that you want to buy!"); + } + int quantity = Integer.parseInt(userInputSplit[2]); + String symbol = userInputSplit[1].substring(1); + BuyCommand buyCommand = new BuyCommand(symbol, quantity); + return buyCommand; + } catch (IndexOutOfBoundsException e) { + throw new PaperTradeException(("Wrong input format! E.g. buy /MSFT 11")); + } catch (NumberFormatException e) { + throw new PaperTradeException("Please enter a valid integer for quantity of stocks that you want to buy!"); + } + } + + public static Command parseSell(String[] userInputSplit) throws PaperTradeException { + try { + if (!userInputSplit[1].startsWith("/")) { + throw new PaperTradeException("Please enter the ticker symbol of the company that you want to buy!"); + } + int quantity = Integer.parseInt(userInputSplit[2]); + String symbol = userInputSplit[1].substring(1); + SellCommand sellCommand = new SellCommand(symbol, quantity); + return sellCommand; + } catch (IndexOutOfBoundsException e) { + throw new PaperTradeException("Wrong input format! E.g. sell /MSFT 11"); + } catch (NumberFormatException e) { + throw new PaperTradeException("Please enter a valid integer for quantity of stocks that you want to sell!"); + } + } + + public static Command parseSearch(String[] userInputSplit) throws PaperTradeException { + try { + if (!userInputSplit[1].startsWith("/")) { + throw new PaperTradeException("Please enter the ticker symbol of the company!"); + } + } catch (IndexOutOfBoundsException e) { + throw new PaperTradeException(("Please enter the ticker symbol of the company you want to search for!")); + } + + SearchCommand searchCommand = new SearchCommand(userInputSplit[1].substring(1)); + + return searchCommand; + } + + public static double parsePrice(double price) { + String strPrice = String.format("%.2f", price); + double formattedPrice = Double.parseDouble(strPrice); + return formattedPrice; + } + +} diff --git a/src/main/java/seedu/duke/ui/Ui.java b/src/main/java/seedu/duke/ui/Ui.java new file mode 100644 index 0000000000..a494ab04ce --- /dev/null +++ b/src/main/java/seedu/duke/ui/Ui.java @@ -0,0 +1,98 @@ +package seedu.duke.ui; + +import seedu.duke.model.Stock; +import seedu.duke.model.Transaction; +import seedu.duke.model.TransactionType; +import seedu.duke.parser.Parser; + +import java.util.ArrayList; +import java.util.Scanner; + +public class Ui { + private final String dividerLine = "____________________________________________________________"; + private final String logo = "__________ ___________ .___\n" + + "\\______ \\_____ ______ ___________ \\__ ___/___________ __| _/____\n" + + " | ___/\\__ \\ \\____ \\_/ __ \\_ __ \\ | | \\_ __ \\__ \\ / __ |/ __ \\\n" + + " | | / __ \\| |_> > ___/| | \\/ | | | | \\// __ \\_/ /_/ \\ ___/\n" + + " |____| (____ / __/ \\___ >__| |____| |__| (____ /\\____ |\\___ >\n" + + " \\/|__| \\/ \\/ \\/ \\/"; + + public void greetUser() { + print(logo); + printWithDivider("Welcome to the Command Line Paper Trading App!"); + } + + public void print(String message) { + System.out.println(message); + } + + public void printWithDivider(String... messages) { + print(dividerLine); + for (String message : messages) { + print(message); + } + print(dividerLine); + } + + public String getUserInput() { + print("What would you like to do today?"); + Scanner scan = new Scanner(System.in); + String userInput = scan.nextLine(); + return userInput; + } + + public void view(ArrayList stocks) { + print(dividerLine); + if (stocks.size() == 0) { + print("You currently have an empty portfolio. Try buying a stock!"); + } + for (int i = 0; i < stocks.size(); i++) { + print((i + 1) + ". " + stocks.get(i).toString()); + int totalStocksBought = 0; + double totalCost = 0; + for (Transaction t : stocks.get(i).getTransactions()) { + print("\t" + t.toString()); + if (t.getTransactionType() == TransactionType.BUY) { + totalCost += t.getUnitPrice() * t.getQuantity(); + totalStocksBought += t.getQuantity(); + } + } + + double totalAsset = stocks.get(i).getLatestPrice() * stocks.get(i).getTotalQuantity(); + double weightedAverageCost = (totalCost / totalStocksBought) * stocks.get(i).getTotalQuantity(); + print("Total holding asset = $" + Parser.parsePrice(totalAsset)); + print("Total cost = $" + Parser.parsePrice(weightedAverageCost)); + + if (totalAsset != 0) { + double earnings = Parser.parsePrice(totalAsset) - Parser.parsePrice(weightedAverageCost); + print("Total Profit/Loss = $" + Parser.parsePrice(earnings)); + } else { + print("Total Profit/Loss = error!"); + } + } + print(dividerLine); + } + + public void viewWallet(double currentAmount, double initialAmount, double allStocksPrice) { + print(dividerLine); + System.out.println("You currently have $" + String.format("%.02f", currentAmount) + " in your wallet."); + System.out.println("Current market value of all your equities owned: $" + + String.format("%.02f", allStocksPrice)); + double difference = (currentAmount + allStocksPrice) - initialAmount; + if (difference >= 0) { + System.out.println("Profit of: +$" + String.format("%.02f", difference)); + } else { + System.out.println("Loss of : -$" + String.format("%.02f", Math.abs(difference))); + } + print(dividerLine); + } + + public void printStocks(ArrayList stocks) { + String[] stockNames = new String[stocks.size()]; + for (int i = 0; i < stocks.size(); i++) { + stockNames[i] = (i + 1) + ". " + stocks.get(i).toString(); + } + printWithDivider(stockNames); + } + +} diff --git a/src/test/java/seedu/duke/DukeTest.java b/src/test/java/seedu/duke/PaperTradeTest.java similarity index 89% rename from src/test/java/seedu/duke/DukeTest.java rename to src/test/java/seedu/duke/PaperTradeTest.java index 2dda5fd651..8d7e155401 100644 --- a/src/test/java/seedu/duke/DukeTest.java +++ b/src/test/java/seedu/duke/PaperTradeTest.java @@ -1,10 +1,10 @@ package seedu.duke; -import static org.junit.jupiter.api.Assertions.assertTrue; - import org.junit.jupiter.api.Test; -class DukeTest { +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PaperTradeTest { @Test public void sampleTest() { assertTrue(true); diff --git a/src/test/java/seedu/duke/model/AddBookmarkCommandTest.java b/src/test/java/seedu/duke/model/AddBookmarkCommandTest.java new file mode 100644 index 0000000000..76dc7699c9 --- /dev/null +++ b/src/test/java/seedu/duke/model/AddBookmarkCommandTest.java @@ -0,0 +1,15 @@ +package seedu.duke.model; + +import org.junit.jupiter.api.Test; +import seedu.duke.command.AddBookmarkCommand; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class AddBookmarkCommandTest { + + @Test + public void getSymbol_noInputRequired_symbolString() { + AddBookmarkCommand addBookmarkCommand = new AddBookmarkCommand("aapl"); + assertEquals(addBookmarkCommand.getSymbol(), "aapl"); + } +} diff --git a/src/test/java/seedu/duke/model/BookmarksTest.java b/src/test/java/seedu/duke/model/BookmarksTest.java new file mode 100644 index 0000000000..e1bb971806 --- /dev/null +++ b/src/test/java/seedu/duke/model/BookmarksTest.java @@ -0,0 +1,33 @@ +package seedu.duke.model; + +import org.junit.jupiter.api.Test; +import seedu.duke.exception.PaperTradeException; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +class BookmarksTest { + + @Test + void addToBookmarks_bookmarkedStock_expectException() { + String symbol = "aapl"; + ArrayList bookmarkedStocks = new ArrayList<>(); + bookmarkedStocks.add(symbol); + Bookmarks bookmarks = new Bookmarks(bookmarkedStocks); + assertThrows(PaperTradeException.class, () -> { + bookmarks.addToBookmarks(symbol); + }); + } + + @Test + void removeBookmark_unbookmarkedStock_expectException() { + String symbol = "aapl"; + ArrayList bookmarkedStocks = new ArrayList(); + Bookmarks bookmarks = new Bookmarks(bookmarkedStocks); + assertThrows(PaperTradeException.class, () -> { + bookmarks.removeBookmark(symbol); + }); + } + +} diff --git a/src/test/java/seedu/duke/model/BuyCommandTest.java b/src/test/java/seedu/duke/model/BuyCommandTest.java new file mode 100644 index 0000000000..8a55f6bb73 --- /dev/null +++ b/src/test/java/seedu/duke/model/BuyCommandTest.java @@ -0,0 +1,21 @@ +package seedu.duke.model; + +import org.junit.jupiter.api.Test; +import seedu.duke.command.BuyCommand; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class BuyCommandTest { + + @Test + public void getSymbol_noInputRequired_symbolString() { + BuyCommand buyCommand = new BuyCommand("aapl", 10); + assertEquals(buyCommand.getSymbol(), "aapl"); + } + + @Test + public void getQuantity_noInputRequired_int() { + BuyCommand buyCommand = new BuyCommand("aapl", 10); + assertEquals(buyCommand.getQuantity(), 10); + } +} diff --git a/src/test/java/seedu/duke/model/InvalidCommandTest.java b/src/test/java/seedu/duke/model/InvalidCommandTest.java new file mode 100644 index 0000000000..7819f72a1b --- /dev/null +++ b/src/test/java/seedu/duke/model/InvalidCommandTest.java @@ -0,0 +1,15 @@ +package seedu.duke.model; + +import org.junit.jupiter.api.Test; +import seedu.duke.command.InvalidCommand; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class InvalidCommandTest { + + @Test + public void getSearchKey_noInputRequired_symbolString() { + InvalidCommand invalidCommand = new InvalidCommand("Invalid command!"); + assertEquals(invalidCommand.getErrorMessage(), "Invalid command!"); + } +} diff --git a/src/test/java/seedu/duke/model/PortfolioTest.java b/src/test/java/seedu/duke/model/PortfolioTest.java new file mode 100644 index 0000000000..5bdb095711 --- /dev/null +++ b/src/test/java/seedu/duke/model/PortfolioTest.java @@ -0,0 +1,65 @@ +package seedu.duke.model; + +import org.junit.jupiter.api.Test; +import seedu.duke.api.StockPriceFetcher; +import seedu.duke.exception.InsufficientQtyException; +import seedu.duke.exception.NegativeQtyException; +import seedu.duke.exception.PaperTradeException; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +class PortfolioTest { + + @Test + void searchStock_invalidStock_expectException() { + StockPriceFetcher stockPriceFetcher = new StockPriceFetcher(); + String symbol = "zzzzzzz"; + assertThrows(PaperTradeException.class, () -> { + stockPriceFetcher.fetchLatestStockData(symbol); + }); + } + + @Test + void sellStock_invalidStock_expectException() { + Portfolio portfolio = new Portfolio(); + StockPriceFetcher stockPriceFetcher = new StockPriceFetcher(); + String symbol = "abcdefg"; + int quantity = 1; + // Use a lambda + assertThrows(PaperTradeException.class, () -> { + portfolio.sellStock(symbol, quantity, stockPriceFetcher.fetchLatestPrice(symbol)); + }); + } + + @Test + void sellStock_higherQuantityThanBought_expectException() { + Portfolio portfolio = new Portfolio(); + StockPriceFetcher stockPriceFetcher = new StockPriceFetcher(); + String symbol = "aapl"; + int buyQuantity = 1; + int sellQuantity = 2; + + // Use a lambda + assertThrows(InsufficientQtyException.class, () -> { + // buy 1 aapl stock + portfolio.buyStock(symbol, buyQuantity, stockPriceFetcher.fetchLatestPrice(symbol)); + // attempt to sell 2 aapl stock + portfolio.sellStock(symbol, sellQuantity, stockPriceFetcher.fetchLatestPrice(symbol)); + }); + } + + @Test + void sellNegativeQuantity() { + Portfolio portfolio = new Portfolio(); + StockPriceFetcher stockPriceFetcher = new StockPriceFetcher(); + String symbol = "aapl"; + + // Use a lambda + assertThrows(NegativeQtyException.class, () -> { + // buy 1 aapl stock + portfolio.buyStock(symbol, 1, stockPriceFetcher.fetchLatestPrice(symbol)); + // attempt to sell -10 aapl stock + portfolio.sellStock(symbol, -10, stockPriceFetcher.fetchLatestPrice(symbol)); + }); + } +} diff --git a/src/test/java/seedu/duke/model/RemoveBookmarkCommandTest.java b/src/test/java/seedu/duke/model/RemoveBookmarkCommandTest.java new file mode 100644 index 0000000000..717d6e0952 --- /dev/null +++ b/src/test/java/seedu/duke/model/RemoveBookmarkCommandTest.java @@ -0,0 +1,15 @@ +package seedu.duke.model; + +import org.junit.jupiter.api.Test; +import seedu.duke.command.RemoveBookmarkCommand; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class RemoveBookmarkCommandTest { + + @Test + public void getSymbol_noInputRequired_symbolString() { + RemoveBookmarkCommand removeBookmarkCommand = new RemoveBookmarkCommand("aapl"); + assertEquals(removeBookmarkCommand.getSymbol(), "aapl"); + } +} diff --git a/src/test/java/seedu/duke/model/SearchCommandTest.java b/src/test/java/seedu/duke/model/SearchCommandTest.java new file mode 100644 index 0000000000..0c74f21b9b --- /dev/null +++ b/src/test/java/seedu/duke/model/SearchCommandTest.java @@ -0,0 +1,15 @@ +package seedu.duke.model; + +import org.junit.jupiter.api.Test; +import seedu.duke.command.SearchCommand; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class SearchCommandTest { + + @Test + public void getSearchKey_noInputRequired_symbolString() { + SearchCommand searchCommand = new SearchCommand("aapl"); + assertEquals(searchCommand.getSearchKey(), "aapl"); + } +} diff --git a/src/test/java/seedu/duke/model/SellCommandTest.java b/src/test/java/seedu/duke/model/SellCommandTest.java new file mode 100644 index 0000000000..16eca2dc6e --- /dev/null +++ b/src/test/java/seedu/duke/model/SellCommandTest.java @@ -0,0 +1,21 @@ +package seedu.duke.model; + +import org.junit.jupiter.api.Test; +import seedu.duke.command.SellCommand; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class SellCommandTest { + + @Test + public void getSymbol_noInputRequired_symbolString() { + SellCommand sellCommand = new SellCommand("aapl", 10); + assertEquals(sellCommand.getSymbol(), "aapl"); + } + + @Test + public void getQuantity_noInputRequired_int() { + SellCommand sellCommand = new SellCommand("aapl", 10); + assertEquals(sellCommand.getQuantity(), 10); + } +} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 892cb6cae7..b2ffedcee0 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,9 +1,14 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - -What is your name? -Hello James Gosling +__________ ___________ .___ +\______ \_____ ______ ___________ \__ ___/___________ __| _/____ + | ___/\__ \ \____ \_/ __ \_ __ \ | | \_ __ \__ \ / __ |/ __ \ + | | / __ \| |_> > ___/| | \/ | | | | \// __ \_/ /_/ \ ___/ + |____| (____ / __/ \___ >__| |____| |__| (____ /\____ |\___ > + \/|__| \/ \/ \/ \/ +____________________________________________________________ +Welcome to the Command Line Paper Trading App! +____________________________________________________________ +What would you like to do today? +____________________________________________________________ +You currently have an empty portfolio. Try buying a stock! +____________________________________________________________ +What would you like to do today? diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index f6ec2e9f95..a9d4eb3811 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -1 +1,2 @@ -James Gosling \ No newline at end of file +view +bye \ No newline at end of file