diff --git a/accounts.go b/accounts.go
index dbbcf6c..b1ebf1d 100644
--- a/accounts.go
+++ b/accounts.go
@@ -2,12 +2,14 @@ package main
import (
"container/ring"
+ "encoding/json"
"errors"
"fmt"
"sync"
"time"
"github.com/distributeddesigns/currency"
+ types "github.com/distributeddesigns/shared_types"
"github.com/gorilla/websocket"
)
@@ -39,6 +41,10 @@ func newAccountForUser(userID string) *account {
return &ac
}
+func (ac *account) toCSV() string {
+ return fmt.Sprintf("%s,%.2f", ac.userID, ac.balance.ToFloat())
+}
+
// AddFunds : Increases the balance of the account
func (ac *account) AddFunds(amount currency.Currency) {
consoleLog.Debugf("Old balance for %s is %s", ac.userID, ac.balance)
@@ -118,13 +124,77 @@ func (ac *account) PruneExpiredTxs() {
}
}
+type pendingATXState struct {
+ Stock string `json:"stock"`
+ Amount string `json:"amount"`
+ Trigger string `json:"trigger"`
+ Action string `json:"action"`
+}
+
+func serializeATX(autoTx types.AutoTxInit) pendingATXState {
+ return pendingATXState{
+ Stock: autoTx.AutoTxKey.Stock,
+ Action: autoTx.AutoTxKey.Action,
+ Amount: autoTx.Amount.String(),
+ Trigger: autoTx.Trigger.String(),
+ }
+}
+
+type accountState struct {
+ Balance string `json:"balance"`
+ Portfolio map[string]uint64 `json:"portfolio"`
+ PendingBuys []pendingTxState `json:"pendingBuys"`
+ PendingSells []pendingTxState `json:"pendingSells"`
+ AutoTx []pendingATXState `json:"pendingATX"`
+}
+
+func (ac *account) GetState() accountState {
+ pendingBuys := make([]pendingTxState, len(ac.pendingBuys))
+ for i, pb := range ac.pendingBuys {
+ pendingBuys[i] = pb.GetState()
+ }
+
+ pendingSells := make([]pendingTxState, len(ac.pendingSells))
+ for i, ps := range ac.pendingSells {
+ pendingSells[i] = ps.GetState()
+ }
+
+ pendingATX := make([]pendingATXState, 0)
+ for k, v := range workATXStore {
+ if k.UserID == ac.userID {
+ serTx := serializeATX(v)
+ pendingATX = append(pendingATX, serTx)
+ }
+ }
+
+ return accountState{
+ Balance: ac.balance.String(),
+ Portfolio: ac.portfolio,
+ PendingBuys: pendingBuys,
+ PendingSells: pendingSells,
+ AutoTx: pendingATX,
+ }
+}
+
+type eventMessage struct {
+ Account accountState `json:"account"`
+ Message string `json:"message"`
+}
+
func (ac *account) PushEvent(message string) {
socket, found := userSocketmap[ac.userID]
if !found {
consoleLog.Errorf("User %s is not subscribed to a socket connection", ac.userID)
return
}
- socket.WriteMessage(websocket.TextMessage, []byte(message))
+
+ payload, err := json.Marshal(&eventMessage{ac.GetState(), message})
+ if err != nil {
+ consoleLog.Errorf("Failed to json-ify %+v, %s", ac, message)
+ return
+ }
+
+ socket.WriteMessage(websocket.TextMessage, payload)
}
type summaryItem struct {
diff --git a/commands_buy.go b/commands_buy.go
index ac6a894..9e6a7ee 100644
--- a/commands_buy.go
+++ b/commands_buy.go
@@ -143,3 +143,11 @@ func (b buyCmd) RollBack() {
func (b buyCmd) IsExpired() bool {
return time.Now().After(b.expiresAt)
}
+
+func (b buyCmd) GetState() pendingTxState {
+ return pendingTxState{
+ Stock: b.stock,
+ Amount: b.purchaseAmount.String(),
+ ExpiresAt: b.expiresAt.Format("15:04:05 Jan _2"),
+ }
+}
diff --git a/commands_sell.go b/commands_sell.go
index 3bebd7e..b800202 100644
--- a/commands_sell.go
+++ b/commands_sell.go
@@ -143,3 +143,11 @@ func (s sellCmd) RollBack() {
func (s sellCmd) IsExpired() bool {
return time.Now().After(s.expiresAt)
}
+
+func (s sellCmd) GetState() pendingTxState {
+ return pendingTxState{
+ Stock: s.stock,
+ Amount: s.profit.String(),
+ ExpiresAt: s.expiresAt.Format("15:04:05 Jan _2"),
+ }
+}
diff --git a/incomingTxWatcher.go b/incomingTxWatcher.go
index 1a71f71..8b7c419 100644
--- a/incomingTxWatcher.go
+++ b/incomingTxWatcher.go
@@ -102,13 +102,20 @@ func wsHandler(w http.ResponseWriter, r *http.Request) {
}
// frontend handshake to get user and hook them into the userMap for sockets
_, message, err := conn.ReadMessage()
+ userID := string(message)
failOnError(err, "Failed to handshake")
- fmt.Printf("Handshake from client is %s\n", message)
- userSocket, found := userSocketmap[string(message)]
+ fmt.Printf("Handshake from client is %s\n", userID)
+ userSocket, found := userSocketmap[string(userID)]
if found {
userSocket.Close()
}
- userSocketmap[string(message)] = conn
+ userSocketmap[string(userID)] = conn
+ if _, accountExists := accountStore[userID]; !accountExists {
+ consoleLog.Infof("Creating account for %s", userID)
+ accountStore[userID] = newAccountForUser(userID)
+ }
+ accountStore[userID].PushEvent("") //shove for userdata
+ //conn.WriteMessage(websocket.TextMessage, []byte(`{"account": "`+accountStore[userID].toCSV()+`", "message": null}`))
}
func incomingTxWatcher() {
diff --git a/pendingTx.go b/pendingTx.go
index 703f141..6b43662 100644
--- a/pendingTx.go
+++ b/pendingTx.go
@@ -1,7 +1,14 @@
package main
+type pendingTxState struct {
+ Stock string `json:"stock"`
+ Amount string `json:"amount"`
+ ExpiresAt string `json:"expiresAt"`
+}
+
type pendingTx interface {
Commit()
RollBack()
IsExpired() bool
+ GetState() pendingTxState
}
diff --git a/receiveAutoTx.go b/receiveAutoTx.go
index 0e5ba4c..406852f 100644
--- a/receiveAutoTx.go
+++ b/receiveAutoTx.go
@@ -69,7 +69,7 @@ func receiveAutoTx() {
autoTxFilled, err := types.ParseAutoTxFilled(string(d.Body[:]))
failOnError(err, "Failed to parse autoTxInit")
consoleLog.Debugf("AutoTxFilled is : %+v\n", autoTxFilled)
- fulfilAutoTx(autoTxFilled)
+ updateAccount(autoTxFilled)
}
fmt.Printf("AutoTxWorker Receiver Spinning\n")
}
diff --git a/sendAutoTx.go b/sendAutoTx.go
index 13c5d12..3ca0a78 100644
--- a/sendAutoTx.go
+++ b/sendAutoTx.go
@@ -6,12 +6,11 @@ import (
"github.com/streadway/amqp"
)
-func fulfilAutoTx(autoTxFilled types.AutoTxFilled) {
- return
-}
-
func updateAccount(autoTxFilled types.AutoTxFilled) {
- // TODO: Do account add and lock here, needs rebase
+ accountStore[autoTxFilled.AutoTxKey.UserID].Lock()
+ accountStore[autoTxFilled.AutoTxKey.UserID].AddFunds(autoTxFilled.AddFunds)
+ accountStore[autoTxFilled.AutoTxKey.UserID].AddStock(autoTxFilled.AutoTxKey.Stock, uint64(autoTxFilled.AddStocks))
+ accountStore[autoTxFilled.AutoTxKey.UserID].Unlock()
return
}
@@ -37,12 +36,23 @@ func sendAutoTx() {
}
if validTrigger {
- // TODO: DO MATH FOR FILLED TRANS
- curr, err := currency.NewFromFloat(0.00)
- failOnError(err, "Failed to parse currency")
+ var filledStock uint
+ var filledCash currency.Currency
+ if autoTxKey.Action == "Buy" {
+ filledStock, filledCash = quote.Price.FitsInto(autoTxInit.Amount)
+ failOnError(err, "Failed to parse currency")
+ } else {
+ numStock, remCash := autoTxInit.Trigger.FitsInto(autoTxInit.Amount) // amount of stock we reserved from their port
+ filledCash = quote.Price
+ err = filledCash.Mul(float64(numStock))
+ filledCash.Add(remCash) // Re-add the unfilled value
+ filledStock = 0
+ failOnError(err, "Failed to parse currency")
+ }
+
autoTxFilled := types.AutoTxFilled{
- AddFunds: curr,
- AddStocks: uint(0),
+ AddFunds: filledCash,
+ AddStocks: filledStock,
AutoTxKey: autoTxKey,
}
updateAccount(autoTxFilled)
diff --git a/static/index.html b/static/index.html
index 32c67c0..81fd3c3 100644
--- a/static/index.html
+++ b/static/index.html
@@ -5,245 +5,28 @@
+
+
-
-
-
-
-
+
diff --git a/static/scripts.js b/static/scripts.js
new file mode 100644
index 0000000..3dafe0c
--- /dev/null
+++ b/static/scripts.js
@@ -0,0 +1,258 @@
+function SendPost(theCommand) {
+ var url = '/push';
+ var request = new XMLHttpRequest();
+ request.open("POST", url, true);
+
+ var jsonPayload = JSON.stringify({command: theCommand})
+ request.send(jsonPayload);
+}
+
+function appendLog(item) {
+ var log = document.getElementById("log");
+ var doScroll = log.scrollTop > log.scrollHeight - log.clientHeight - 1;
+ log.appendChild(item);
+ if (doScroll) {
+ log.scrollTop = log.scrollHeight - log.clientHeight;
+ }
+}
+
+function doAuth(rawData) {
+ localStorage["user"] = localStorage["user"] || document.getElementById("userid").value
+ document.getElementById('loginview').style.display = 'none';
+ document.getElementById('authview').style.display = 'block';
+ var conn;
+ var msg = document.getElementById("msg");
+ if (window["WebSocket"]) {
+ conn = new WebSocket("ws://" + document.location.host + "/ws");
+ conn.onopen = function (evt) {
+ console.log("CON OPEN")
+ conn.send(localStorage["user"])
+ }
+ conn.onclose = function (evt) {
+ var item = document.createElement("div");
+ item.innerHTML = "
Connection closed.";
+ appendLog(item);
+ };
+ conn.onmessage = function (evt) {
+ jsonData = JSON.parse(evt.data)
+
+ message = jsonData["message"]
+ if (message !== "") {
+ var item = document.createElement("div");
+ item.innerText = message;
+ appendLog(item)
+ }
+
+ userData = jsonData["account"]
+ document.getElementById("balance").innerHTML = userData["balance"]
+
+ buyContainer = document.createElement("div")
+ userData["pendingBuys"].map(function(elem) {
+ var item = document.createElement("div");
+ item.innerText = "{" + elem.amount + "," + elem.expiresAt + "," + elem.stock + "}"
+ buyContainer.appendChild(item)
+ })
+ document.getElementById("pendingbuys").innerHTML = buyContainer.innerHTML
+
+ sellContainer = document.createElement("div")
+ userData["pendingSells"].map(function(elem) {
+ var item = document.createElement("div");
+ item.innerText = "{" + elem.amount + "," + elem.expiresAt + "," + elem.stock + "}"
+ sellContainer.appendChild(item)
+ })
+ document.getElementById("pendingsells").innerHTML = sellContainer.innerHTML
+
+ portfolioContainer = document.createElement("div")
+ portfolio = userData["portfolio"]
+ for (var key in portfolio) {
+ if (portfolio.hasOwnProperty(key)) {
+ var item = document.createElement("div");
+ item.innerText = key + " -> " + portfolio[key];
+ portfolioContainer.appendChild(item)
+ }
+ }
+ document.getElementById("portfolio").innerHTML = portfolioContainer.innerHTML
+
+ autoTxContainer = document.createElement("div")
+ userData["pendingATX"].map(function(elem) {
+ var item = document.createElement("div");
+ item.innerText = "{" + elem.stock + "," + elem.amount + "," + elem.trigger + "," + elem.action + "}"
+ autoTxContainer.appendChild(item)
+ })
+ document.getElementById("autotx").innerHTML = autoTxContainer.innerHTML
+ };
+ } else {
+ var item = document.createElement("div");
+ item.innerHTML = "
Your browser does not support WebSockets.";
+ appendLog(item);
+ }
+ console.log("Auth Successful")
+}
+function Auth() {
+ var request = new XMLHttpRequest();
+ request.open('POST', '/auth', true);
+
+ request.onload = function() {
+ if (request.status >= 200 && request.status < 400) {
+ // Success!
+ // var data = request.responseText;
+ // console.log(data)
+ doAuth(request.responseText)
+ } else {
+ // We reached our target server, but it returned an error
+ console.log("Failed to Auth user")
+ }
+ };
+
+ request.onerror = function() {
+ // There was a connection error of some sort
+ };
+
+ request.send(JSON.stringify({user: document.getElementById("userid").value, pass: document.getElementById("pwd").value}));
+}
+function Create() {
+ var request = new XMLHttpRequest();
+ request.open('POST', '/create', true);
+
+ request.onload = function() {
+ if (request.status >= 200 && request.status < 400) {
+ // Success!
+ // var data = request.responseText;
+ // console.log(data)
+ doAuth(request.responseText)
+ console.log("Create Successful")
+ } else {
+ // We reached our target server, but it returned an error
+ console.log("Failed to create user")
+ }
+ };
+
+ request.onerror = function() {
+ // There was a connection error of some sort
+ };
+
+ request.send(JSON.stringify({user: document.getElementById("userid").value, pass: document.getElementById("pwd").value}));
+}
+
+function AmountCheck(theAmount){
+ var reg = /^\d+\.?(\d\d)?$/
+ if (reg.test(document.getElementById("addamount").value)) {
+ return true
+ }
+ else{
+ var item = document.createElement("div");
+ item.innerText = "Invalid input for amount. Expected format example: '100.00'"
+ appendLog(item)
+ return false
+ }
+}
+
+function SymbolCheck(theStockSymbol){
+ var reg = /^[A-Z][A-Z][A-Z]$/
+ if (reg.test(theStockSymbol)) {
+ return true
+ }
+ else{
+ var item = document.createElement("div");
+ item.innerText = "Invalid input for stock symbol. Expected 3 letter upper string. Format example: 'ASS'"
+ appendLog(item)
+ return false
+ }
+}
+
+function Logout() {
+ localStorage.clear()
+ location.reload()
+}
+function Add() {
+ if (AmountCheck(document.getElementById("addamount").value)) {
+ SendPost("ADD,"+localStorage["user"]+","+document.getElementById("addamount").value)
+ console.log("Add")
+ }
+}
+function Quote() {
+ if (SymbolCheck(document.getElementById("quotestocksymbol").value)) {
+ SendPost("QUOTE,"+localStorage["user"]+","+document.getElementById("quotestocksymbol").value.toUpperCase())
+ console.log("Quote")
+ }
+}
+function Buy() {
+ if(AmountCheck(document.getElementById("buyamount").value) && SymbolCheck(document.getElementById("buystocksymbol").value)){
+ SendPost("BUY,"+localStorage["user"]+","+document.getElementById("buystocksymbol").value.toUpperCase()+","+document.getElementById("buyamount").value)
+ console.log("Buy")
+ }
+}
+function CommitBuy() {
+ SendPost("COMMIT_BUY,"+localStorage["user"])
+ console.log("Commit Buy")
+}
+function CancelBuy() {
+ SendPost("CANCEL_BUY,"+localStorage["user"])
+ console.log("Cancel Buy")
+}
+function Sell() {
+ if(AmountCheck(document.getElementById("sellamount").value) && SymbolCheck(document.getElementById("sellstocksymbol").value)){
+ SendPost("SELL,"+localStorage["user"]+","+document.getElementById("sellstocksymbol").value.toUpperCase()+","+document.getElementById("sellamount").value)
+ console.log("Sell")
+ }
+}
+function CommitSell() {
+ SendPost("COMMIT_SELL,"+localStorage["user"])
+ console.log("Commit Sell")
+}
+function CancelSell() {
+ SendPost("CANCEL_SELL,"+localStorage["user"])
+ console.log("Cancel Sell")
+}
+function SetBuyAmount() {
+ if(AmountCheck(document.getElementById("setbuyamount").value) && SymbolCheck(document.getElementById("setbuystocksymbol").value)){
+ SendPost("SET_BUY_AMOUNT,"+localStorage["user"]+","+document.getElementById("setbuystocksymbol").value.toUpperCase()+","+document.getElementById("setbuyamount").value)
+ console.log("Set buy amount")
+ }
+}
+function CancelSetBuy() {
+ if(AmountCheck(SymbolCheck(document.getElementById("cancelbuystocksymbol").value))) {
+ SendPost("CANCEL_SET_BUY,"+localStorage["user"]+","+document.getElementById("cancelbuystocksymbol").value.toUpperCase())
+ console.log("Cancel set buy")
+ }
+}
+function SetBuyTrigger() {
+ if(AmountCheck(SymbolCheck(document.getElementById("setbuytrigstocksymbol").value))) {
+ SendPost("SET_BUY_TRIGGER,"+localStorage["user"]+","+document.getElementById("setbuytrigstocksymbol").value.toUpperCase()+","+document.getElementById("setbuytrigamount").value)
+ console.log("Set buy trigger")
+ }
+}
+function SetSellAmount() {
+ if(AmountCheck(document.getElementById("setsellamount").value) && SymbolCheck(document.getElementById("setsellstocksymbol").value)){
+ SendPost("SET_SELL_AMOUNT,"+localStorage["user"]+","+document.getElementById("setsellstocksymbol").value.toUpperCase()+","+document.getElementById("setsellamount").value)
+ console.log("Set Sell Amount")
+ }
+}
+function CancelSetSell() {
+ if(AmountCheck(SymbolCheck(document.getElementById("cancelsellstocksymbol").value))) {
+ SendPost("CANCEL_SET_SELL,"+localStorage["user"]+","+document.getElementById("cancelsellstocksymbol").value.toUpperCase())
+ console.log("Cancel set sell")
+ }
+}
+function SetSellTrigger() {
+ if(AmountCheck(SymbolCheck(document.getElementById("setselltrigstocksymbol").value))) {
+ SendPost("SET_SELL_TRIGGER,"+localStorage["user"]+","+document.getElementById("setselltrigstocksymbol").value.toUpperCase()+","+document.getElementById("setselltrigamount").value)
+ console.log("Set sell trigger")
+ }
+}
+function Dumplog() {
+ var userID = localStorage["user"]
+ if(userID === "admin"){
+ SendPost("DUMPLOG,out.dump")
+ console.log("Admin Dump")
+ }
+ else
+ {
+ SendPost("DUMPLOG,"+localStorage["user"]+","+document.getElementById("dumplogfile").value)
+ console.log("User Dump")
+ }
+}
+function DisplaySummary() {
+ SendPost("DISPLAY_SUMMARY,"+localStorage["user"])
+ console.log("Display summary")
+}
diff --git a/static/styles.css b/static/styles.css
new file mode 100644
index 0000000..31180d1
--- /dev/null
+++ b/static/styles.css
@@ -0,0 +1,44 @@
+div form, div h2 {
+ margin-left: 20px;
+ margin-right: 20px;
+}
+
+#log {
+ height: 32em;
+ margin: 10px;
+ overflow-y: scroll;
+}
+
+#userinfocontainer {
+ height: 25em;
+ width: 60%;
+}
+
+
+
+.cmdwrapper {
+ float: left;
+ width: 30%;
+ margin: 5px 20px;
+ height: 100%;
+ overflow-y: scroll;
+}
+
+#authview {
+ display: none;
+ height: 66.5em;
+}
+
+#loginview {
+ display: none;
+ margin: 5px 20px;
+}
+
+#statwrapper {
+ float: right;
+ width: 65%;
+}
+
+#userid, #pwd {
+ width: 200px;
+}