Ada Trader
-
-
-
-
-
- Ada Trader
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
diff --git a/spec/models/order_spec.js b/spec/models/order_spec.js
new file mode 100644
index 0000000..f930f1d
--- /dev/null
+++ b/spec/models/order_spec.js
@@ -0,0 +1,125 @@
+import Backbone from 'backbone';
+import _ from 'underscore';
+
+import Quote from 'models/quote';
+import Order from 'models/order';
+
+describe('model validations', () => {
+ let quote;
+ let buyOrder;
+ let sellOrder;
+ beforeEach(() => {
+ quote = new Quote({
+ symbol: 'TESTQUOTE',
+ price: 100.00,
+ });
+ buyOrder = new Order({
+ symbol: 'TESTQUOTE',
+ targetPrice: 90.00,
+ buy: true,
+ activeQuote: quote,
+ symbolList: ['TESTQUOTE']
+ });
+ sellOrder = new Order({
+ symbol: 'TESTQUOTE',
+ targetPrice: 101.00,
+ buy: false,
+ activeQuote: quote,
+ symbolList: ['TESTQUOTE']
+ });
+ });
+
+ it('valid sell order will work', () => {
+ expect(sellOrder.isValid()).toEqual(true);
+ });
+
+ it('valid buy order will work', () => {
+ expect(buyOrder.isValid()).toEqual(true);
+ });
+
+
+ it('price cannot be blank', () => {
+ buyOrder.set('targetPrice', '');
+ expect(buyOrder.isValid()).toEqual(false);
+ });
+
+ it('price cannot be a negative number', () => {
+ buyOrder.set('targetPrice', -1);
+ expect(buyOrder.isValid()).toEqual(false);
+ });
+
+ it('symbol cannot be blank', () => {
+ buyOrder.set('symbol', '');
+ expect(buyOrder.isValid()).toEqual(false);
+ });
+
+ it('buy price cannot be equal to current price', () => {
+ buyOrder.set('targetPrice', 100.00);
+ expect(buyOrder.isValid()).toEqual(false);
+ });
+
+ it('buy price cannot be higher than current price', () => {
+ buyOrder.set('targetPrice', 101.00);
+ expect(buyOrder.isValid()).toEqual(false);
+ });
+
+
+ it('sell price cannot be lower than current price', () => {
+ sellOrder.set('targetPrice', 100.00);
+ expect(sellOrder.isValid()).toEqual(false);
+ });
+
+ it('sell price cannot be equal to current price', () => {
+ sellOrder.set('targetPrice', 100.00);
+ expect(sellOrder.isValid()).toEqual(false);
+ });
+});
+
+describe('model custom methods', () => {
+ let testOrder1;
+ let testOrder2;
+ let bus;
+ beforeEach(() => {
+ bus = {};
+ bus = _.extend(bus, Backbone.Events);
+
+ const quote = new Quote({
+ symbol: 'HELLO',
+ price: 100.00,
+ });
+
+ const buyOrderData = {
+ symbol: 'HELLO',
+ targetPrice: 99.00,
+ buy: true,
+ bus: bus,
+ activeQuote: quote,
+ symbolList: ['HELLO']
+ }
+
+ const sellOrderData = {
+ symbol: 'HELLO',
+ targetPrice: 99.00,
+ buy: false,
+ bus: bus,
+ activeQuote: quote,
+ symbolList: ['HELLO']
+ }
+
+ testOrder1 = new Order(buyOrderData);
+ testOrder2 = new Order(sellOrderData);
+});
+
+ it('valid buy order triggers bus', () => {
+ const spy = spyOn(bus, 'trigger');
+ testOrder1.quote.set('price', 50.00);
+ expect(spy).toHaveBeenCalled();
+ });
+
+ it('valid sell order triggers bus', () => {
+ const spy = spyOn(bus, 'trigger');
+ testOrder2.quote.set('price', 101.00);
+ expect(spy).toHaveBeenCalled();
+ });
+
+});
diff --git a/src/app.js b/src/app.js
index 03ec910..114c357 100644
--- a/src/app.js
+++ b/src/app.js
@@ -2,9 +2,22 @@ import 'foundation-sites/dist/foundation.css';
import 'css/app.css';
import $ from 'jquery';
-
+import _ from 'underscore';
import Simulator from 'models/simulator';
+
+import Quote from 'models/quote';
+import QuoteView from 'views/quote_view';
import QuoteList from 'collections/quote_list';
+import QuoteListView from 'views/quote_list_view';
+
+import Order from 'models/order';
+import OrderList from 'collections/order_list';
+import OrderView from 'views/order_view';
+import OrderListView from 'views/order_list_view';
+
+let bus = {};
+bus = _.extend(bus, Backbone.Events);
+const quoteList = new QuoteList();
const quoteData = [
{
@@ -32,4 +45,21 @@ $(document).ready(function() {
});
simulator.start();
+
+ const quoteListView = new QuoteListView({
+ model: quotes,
+ template: _.template($('#quote-template').html()),
+ el: '#quotes-container',
+ bus: bus
+ });
+ quoteListView.render();
+
+ const orders = new OrderList();
+ const orderListView = new OrderListView({
+ model: orders,
+ symbols: quotes,
+ template: _.template($('#order-template').html()),
+ el: '#order-workspace',
+ bus: bus
+ });
});
diff --git a/src/collections/order_list.js b/src/collections/order_list.js
new file mode 100644
index 0000000..6b77957
--- /dev/null
+++ b/src/collections/order_list.js
@@ -0,0 +1,8 @@
+import Backbone from 'backbone';
+import Order from '../models/order';
+
+const OrderList = Backbone.Collection.extend({
+ model: Order,
+});
+
+export default OrderList;
diff --git a/src/models/order.js b/src/models/order.js
new file mode 100644
index 0000000..9adc924
--- /dev/null
+++ b/src/models/order.js
@@ -0,0 +1,57 @@
+import Backbone from 'backbone';
+import QuoteList from 'collections/quote_list';
+
+const Order = Backbone.Model.extend({
+ defaults: {
+ symbol: '',
+ targetPrice: '',
+ buy: '',
+ },
+ initialize(attributes) {
+ this.buy = attributes.buy;
+ this.targetPrice = attributes.targetPrice
+ this.quote = attributes.activeQuote;
+ this.symbolList = attributes.symbolList;
+ this.listenTo(this.quote, 'change', this.quoteCheck);
+ this.bus = attributes.bus;
+ },
+ validate(attributes) {
+ const errors = {};
+
+ if (!attributes.targetPrice) {
+ errors['targetPrice'] = ['Price is cannot be blank'];
+ }
+
+ if (attributes.targetPrice <= 0) {
+ errors['targetPrice'] = ['Price can not be a negative number']
+ }
+
+ if (!attributes.symbol) {
+ errors['symbol'] = ['Symbol can not be blank'];
+ }
+
+ if (attributes.buy && parseFloat(attributes.targetPrice) >= parseFloat(attributes.activeQuote.get('price'))) {
+ errors['targetPrice'] = ['Buy order price too high']
+ }
+
+ if (!attributes.buy && parseFloat(attributes.targetPrice) <= parseFloat(attributes.activeQuote.get('price'))) {
+ errors['targetPrice'] = ['Sell order price too low']
+ }
+
+ if ( Object.keys(errors).length > 0 ) {
+ return errors;
+ } else {
+ return false;
+ }
+ },
+
+ quoteCheck: function(e) {
+ if (this.buy && parseFloat(this.targetPrice) > parseFloat(this.quote.get('price'))) {
+ this.bus.trigger(`buyMe${this.quote.attributes.symbol}`, this.quote.attributes);
+ } else if (!this.buy && parseFloat(this.targetPrice) < parseFloat(this.quote.get('price'))) {
+ this.bus.trigger(`sellMe${this.quote.attributes.symbol}`, this.quote.attributes);
+ }
+ },
+});
+
+export default Order;
diff --git a/src/models/quote.js b/src/models/quote.js
index 4fbf466..bdd2ac0 100644
--- a/src/models/quote.js
+++ b/src/models/quote.js
@@ -7,11 +7,16 @@ const Quote = Backbone.Model.extend({
},
buy() {
- // Implement this function to increase the price by $1.00
+ const current_price = this.get('price');
+ this.set('price', current_price + 1.00);
+ console.log(`BUYING ${this.get('symbol')} at ${this.get('price')}`);
},
sell() {
- // Implement this function to decrease the price by $1.00
+ const current_price = this.get('price');
+ this.set('price', current_price - 1.00);
+ console.log(`SELLING ${this.get('symbol')} at ${this.get('price')}`);
+
},
});
diff --git a/src/views/order_list_view.js b/src/views/order_list_view.js
new file mode 100644
index 0000000..afe063b
--- /dev/null
+++ b/src/views/order_list_view.js
@@ -0,0 +1,91 @@
+import Backbone from 'backbone';
+import _ from 'underscore';
+import $ from 'jquery';
+
+import Order from '../models/order';
+import OrderView from '../views/order_view';
+
+import Quote from '../models/quote';
+import QuoteList from '../collections/quote_list';
+
+const OrderListView = Backbone.View.extend({
+ initialize(params) {
+ this.template = params.template;
+ this.quoteList = params.symbols
+ this.bus = params.bus;
+ this.listenTo(this.model, 'update', this.render);
+ this.orderForm(params.symbols.models);
+ },
+
+ render() {
+ this.$('#orders').empty();
+ this.model.each((order) => {
+ const orderView = new OrderView({
+ model: order,
+ template: this.template,
+ tagName: 'li',
+ className: 'order',
+ bus: this.bus,
+ });
+ this.$('#orders').append(orderView.render().$el);
+ });
+ return this;
+ },
+ orderForm(symbol_names) {
+ symbol_names.forEach( (symbol_name) => {
+ $('select').append(``)
+ });
+ },
+ events: {
+ 'click button.btn-buy': 'buyOrder',
+ 'click button.btn-sell': 'sellOrder'
+ },
+ buyOrder: function(event) {
+ event.preventDefault();
+ const orderData = {};
+ orderData['symbol'] = this.$(`[name=symbol]`).val();
+ orderData['targetPrice'] = parseFloat(this.$(`[name=price-target]`).val());
+ orderData['buy'] = true;
+ const searchElem = this.quoteList.findWhere({symbol: orderData['symbol']});
+ orderData['activeQuote'] = searchElem;
+ orderData['bus'] = this.bus;
+ orderData['symbolList'] = this.quoteList.map(quote => quote.get('symbol'));
+ const newOrder = new Order(orderData);
+ if (newOrder.isValid()) {
+ this.model.add(newOrder);
+ } else {
+ newOrder.destroy();
+ this.showMessage(newOrder.validationError);
+ }
+ },
+ sellOrder: function(event) {
+ event.preventDefault();
+ const orderData = {};
+ orderData['symbol'] = this.$(`[name=symbol]`).val();
+ orderData['targetPrice'] = parseInt(this.$(`[name=price-target]`).val());
+ orderData['buy'] = false;
+ orderData['bus'] = this.bus;
+ const searchElem = this.quoteList.findWhere({symbol: orderData['symbol']})
+ orderData['activeQuote'] = searchElem
+ orderData['symbolList'] = this.quoteList.map(quote => quote.get('symbol'));
+ const newOrder = new Order(orderData);
+ if (newOrder.isValid()) {
+ this.model.add(newOrder);
+ } else {
+ this.showMessage(newOrder.validationError);
+ }
+ },
+ showMessage: function(messageObj) {
+ const messageArea = this.$('.form-errors');
+ messageArea.empty();
+ _.each(messageObj, (messages) => {
+ messages.forEach((text) => {
+ messageArea.append(`Quotes
-
-
+
+ -
-
Ada Trader
+
+
+
-
+
+
+
- Quotes
+
+
-
-
+
+
+
+
-
+
-
+ Trade History
-
-
+ -
-
+
+
Trade History
+
+
-
-
+
@@ -53,13 +54,12 @@