-
Notifications
You must be signed in to change notification settings - Fork 43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pipes - Rebecca - Ada Trader #34
base: master
Are you sure you want to change the base?
Changes from all commits
1b29b36
0d08a66
5f15c9b
554a3a3
b6a7c9c
bb81327
6e322b9
69c732d
712bd48
9ecd646
9d7699a
4345a25
9939d11
609dee4
5cb59cb
0c29ff4
8dbec33
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import OpenOrder from 'models/open_order'; | ||
import Quote from 'models/quote'; | ||
|
||
|
||
describe('OpenOrder spec', () => { | ||
let currentQuote; | ||
let OrderAtQuotePrice; | ||
let OrderAboveQuotePrice; | ||
let OrderBelowQuotePrice; | ||
|
||
beforeEach(() => { | ||
currentQuote = new Quote({ | ||
symbol: 'HUMOR', | ||
price: 85.00, | ||
}) | ||
|
||
OrderAtQuotePrice = new OpenOrder({ | ||
targetPrice: 85.0, | ||
symbol: 'HUMOR', | ||
quote: currentQuote, | ||
buy: true, | ||
}) | ||
|
||
OrderAboveQuotePrice = new OpenOrder({ | ||
targetPrice: 99.0, | ||
symbol: 'HUMOR', | ||
quote: currentQuote, | ||
buy: true, | ||
}) | ||
|
||
OrderBelowQuotePrice = new OpenOrder({ | ||
targetPrice: 83.0, | ||
symbol: 'HUMOR', | ||
quote: currentQuote, | ||
buy: true, | ||
}) | ||
}); | ||
|
||
describe('validate', () => { | ||
it ('does not allow for price to be blank', () => { | ||
|
||
const invalidOrder = new OpenOrder({ | ||
targetPrice: '', | ||
symbol: 'HUMOR', | ||
quote: currentQuote, | ||
buy: true, | ||
}) | ||
|
||
expect(invalidOrder.isValid()).toBeFalsy(); | ||
}); | ||
|
||
// buy order validations | ||
it ('does not allow for buy orders where target price is equal to the quote', () => { | ||
|
||
expect(OrderAtQuotePrice.isValid()).toBeFalsy(); | ||
}); | ||
|
||
it ('does not allow for buy orders where target price is greater than the quote', () => { | ||
|
||
expect(OrderAboveQuotePrice.isValid()).toBeFalsy(); | ||
}); | ||
|
||
it ('allows for buy orders where target price is less than the quote', () => { | ||
|
||
expect(OrderBelowQuotePrice.isValid()).toBeTruthy(`error: ${OrderBelowQuotePrice.validationError}`); | ||
}); | ||
|
||
// sell order validations | ||
it ('does not allow for sell orders where target price is equal to the quote', () => { | ||
|
||
OrderAtQuotePrice.set('buy', false); | ||
|
||
expect(OrderAtQuotePrice.isValid()).toBeFalsy(); | ||
}); | ||
|
||
it ('does not allow for sell orders where target price is less than the quote', () => { | ||
|
||
OrderBelowQuotePrice.set('buy', false); | ||
|
||
expect(OrderBelowQuotePrice.isValid()).toBeFalsy(); | ||
}); | ||
|
||
it ('allows for sell orders where target price is greater than the quote', () => { | ||
|
||
OrderAboveQuotePrice.set('buy', false) | ||
|
||
expect(OrderAboveQuotePrice.isValid()).toBeTruthy(`error: ${OrderAboveQuotePrice.validationError}`); | ||
}); | ||
}); | ||
|
||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import Backbone from 'backbone'; | ||
import OpenOrder from 'models/open_order'; | ||
|
||
const OpenOrderList = Backbone.Collection.extend({ | ||
model: OpenOrder, | ||
}); | ||
|
||
export default OpenOrderList; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import Backbone from 'backbone'; | ||
|
||
const OpenOrder = Backbone.Model.extend({ | ||
initialize(attributes) { | ||
}, | ||
validate(attributes) { | ||
const errors = {}; | ||
|
||
if (!attributes.targetPrice) { | ||
errors['target_price'] = ["Price cannot be blank."]; | ||
} | ||
|
||
if (attributes.buy && attributes.targetPrice >= attributes.quote.get('price')) { | ||
errors['target_price'] = ["The price you listed for a buy order is greater than or equal to the current quote price."]; | ||
} | ||
|
||
if (!attributes.buy && attributes.targetPrice <= attributes.quote.get('price')) { | ||
errors['target_price'] = ["The price you listed for a sell order is less than or equal to the current quote price."]; | ||
} | ||
|
||
if ( Object.keys(errors).length > 0 ) { | ||
return errors; | ||
} else { | ||
return false; | ||
} | ||
}, | ||
}); | ||
|
||
export default OpenOrder; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import Backbone from 'backbone'; | ||
import OpenOrderView from '../views/open_order_view' | ||
|
||
import OpenOrder from '../models/open_order'; | ||
|
||
const OpenOrderListView = Backbone.View.extend({ | ||
initialize(params) { | ||
this.template = params.template; | ||
this.bus = params.bus; | ||
this.listenTo(this.model, 'update', this.render); | ||
this.listenTo(this.bus, 'quote_symbols', this.symbolDropdown); | ||
this.listenTo(this.bus, 'quote_change', this.checkOpenOrders); | ||
|
||
// list for current quote list | ||
this.listenTo(this.bus, 'current_quote_list', this.getQuoteList); | ||
}, | ||
getQuoteList(quoteList) { | ||
this.quoteList = quoteList; | ||
console.log('the quote list !!') | ||
console.log(this.quoteList); | ||
|
||
}, | ||
render() { | ||
this.$('#orders').empty(); | ||
|
||
this.model.each((openOrder) => { | ||
const openOrderView = new OpenOrderView({ | ||
model: openOrder, | ||
template: this.template, | ||
tagName: 'li', | ||
className: 'order', | ||
bus: this.bus, | ||
}); | ||
// render returns the taskview (this) which allows you to append | ||
this.$('#orders').append(openOrderView.render().$el); | ||
}); | ||
return this; | ||
}, | ||
checkOpenOrders(quote) { | ||
|
||
let openOrders = this.model.where({symbol: quote.get('symbol')}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently, the workflow for executing an open order looks like this:
But we can simplify this! The key observation is that each
This eliminates dependencies on the bus and the |
||
if (openOrders.length >= 1) { | ||
openOrders.forEach((openOrder) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't need to wrap the call to |
||
// if you are buying | ||
if (openOrder.get('buy') && openOrder.get('targetPrice') >= quote.get('price')) { | ||
let tradeObject = { | ||
price: openOrder.get('targetPrice'), | ||
symbol: openOrder.get('symbol'), | ||
buy: true, | ||
} | ||
this.bus.trigger('add_trade', tradeObject) | ||
// destroy purchased open order | ||
openOrder.destroy(); | ||
// make sure quote is recognized as bought so price increases appropriately | ||
quote.buy(); | ||
} | ||
else if ((!openOrder.get('buy')) && openOrder.get('targetPrice') <= quote.get('price')) { | ||
let tradeObject = { | ||
price: openOrder.get('targetPrice'), | ||
symbol: openOrder.get('symbol'), | ||
buy: false, | ||
} | ||
this.bus.trigger('add_trade', tradeObject) | ||
// destroy purchased open order | ||
openOrder.destroy(); | ||
// make sure quote is recognized as sold so price decreases appropriately | ||
quote.sell(); | ||
} | ||
}); | ||
} | ||
}, | ||
symbolDropdown(quotes) { | ||
quotes.forEach((quote) => { | ||
this.$('select[name=symbol]').append(`<option value="${quote}">${quote}</option>`) | ||
}); | ||
}, | ||
events: { | ||
'click button.btn-buy': 'addBuyOpenOrder', | ||
'click button.btn-sell': 'addSellOpenOrder', | ||
}, | ||
updateStatusMessageFrom(messageHash) { | ||
const $formErrors = this.$('.form-errors'); | ||
|
||
$formErrors.empty(); | ||
Object.keys(messageHash).forEach((messageType) => { | ||
messageHash[messageType].forEach((message) => { | ||
$formErrors.append(`<li>${message}</li>`); | ||
}); | ||
$formErrors.show(); | ||
}); | ||
}, | ||
// TODO: consolidate addBuyOpenOrder and addSellOpenOrder | ||
addBuyOpenOrder(event) { | ||
event.preventDefault(); | ||
|
||
let formData = this.getFormData(); | ||
|
||
formData['buy'] = true; | ||
let correctQuote = this.quoteList.findWhere({symbol: formData['symbol']}) | ||
formData['quote'] = correctQuote; | ||
const newOpenOrder = new OpenOrder(formData); | ||
if (newOpenOrder.isValid()) { | ||
this.model.add(newOpenOrder); | ||
this.clearFormData(); | ||
console.log('in add buy open order'); | ||
} else { | ||
console.log('ERROR'); | ||
this.updateStatusMessageFrom(newOpenOrder.validationError); | ||
newOpenOrder.destroy(); | ||
} | ||
}, | ||
addSellOpenOrder(event) { | ||
event.preventDefault(); | ||
|
||
let formData = this.getFormData(); | ||
|
||
formData['buy'] = false; | ||
|
||
let correctQuote = this.quoteList.findWhere({symbol: formData['symbol']}) | ||
formData['quote'] = correctQuote; | ||
|
||
const newOpenOrder = new OpenOrder(formData); | ||
// if is valid add newTask and clearformdata | ||
if (newOpenOrder.isValid()) { | ||
this.model.add(newOpenOrder); | ||
this.clearFormData(); | ||
console.log('in add sell open order'); | ||
} else { | ||
console.log('ERROR'); | ||
this.updateStatusMessageFrom(newOpenOrder.validationError); | ||
newOpenOrder.destroy(); | ||
} | ||
}, | ||
|
||
getFormData() { | ||
console.log('in get form data'); | ||
const openOrderData = {}; | ||
['symbol', 'price-target'].forEach((field) => { | ||
let val; | ||
if (field === 'symbol') { | ||
val = | ||
this.$(`.order-entry-form select[name=${field}]`).val(); | ||
} else { | ||
val = | ||
parseFloat(this.$(`.order-entry-form input[name=${field}]`).val()); | ||
field = 'targetPrice'; | ||
} | ||
if (val !== '') { | ||
openOrderData[field] = val; | ||
} | ||
}); | ||
|
||
return openOrderData; | ||
}, | ||
clearFormData () { | ||
['symbol', 'price-target'].forEach((field) => { | ||
if (field === 'symbol') { | ||
this.$(`.order-entry-form select[name=${field}]`).val(''); | ||
} else { | ||
this.$(`.order-entry-form input[name=${field}]`).val(''); | ||
} | ||
}); | ||
}, | ||
}); | ||
|
||
|
||
export default OpenOrderListView; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import Backbone from 'backbone'; | ||
import OpenOrder from '../models/open_order'; | ||
|
||
const OpenOrderView = Backbone.View.extend({ | ||
initialize(params) { | ||
this.template = params.template; | ||
this.bus = params.bus; | ||
this.listenTo(this.model, 'change', this.render); | ||
}, | ||
|
||
render() { | ||
const compiledTemplate = this.template(this.model.toJSON()); | ||
this.$el.html(compiledTemplate); | ||
return this; | ||
}, | ||
events: { | ||
'click button.btn-cancel': 'deleteOpenOrder', | ||
}, | ||
|
||
deleteOpenOrder(event) { | ||
this.model.destroy(); | ||
this.remove(); | ||
}, | ||
|
||
}); | ||
|
||
export default OpenOrderView; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import Backbone from 'backbone'; | ||
import QuoteView from '../views/quote_view' | ||
|
||
import Quote from '../models/quote'; | ||
|
||
const QuoteListView = Backbone.View.extend({ | ||
initialize(params) { | ||
this.template = params.template; | ||
this.listenTo(this.model, 'update', this.render); | ||
this.bus = params.bus; | ||
}, | ||
render() { | ||
this.$('#quotes').empty(); | ||
console.log('in quotelistview render'); | ||
console.log(this.model) | ||
this.model.each((quote) => { | ||
const quoteView = new QuoteView({ | ||
model: quote, | ||
template: this.template, | ||
tagName: 'li', | ||
className: 'quote', | ||
bus: this.bus, | ||
}); | ||
this.$('#quotes').append(quoteView.render().$el); | ||
// pass the quotelist to the open order view | ||
this.bus.trigger('current_quote_list', this.model) | ||
|
||
}); | ||
// symbols() is a custom function in the collection that returns an array of models' symbols | ||
let quotes = this.model.symbols(); | ||
this.bus.trigger('quote_symbols', quotes); | ||
return this; | ||
}, | ||
}); | ||
|
||
export default QuoteListView; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import Backbone from 'backbone'; | ||
import Quote from '../models/quote'; | ||
|
||
const QuoteView = Backbone.View.extend({ | ||
|
||
initialize(params) { | ||
this.template = params.template; | ||
this.bus = params.bus; | ||
this.listenTo(this.model, 'change', this.render); | ||
}, | ||
events: { | ||
|
||
'click button.btn-buy': 'buyOrSellQuote', | ||
'click button.btn-sell': 'buyOrSellQuote', | ||
}, | ||
buyOrSellQuote(event) { | ||
|
||
let tradeObject = { | ||
price: this.model.get('price'), | ||
symbol: this.model.get('symbol'), | ||
} | ||
|
||
if (event.currentTarget.innerHTML === 'Buy') { | ||
tradeObject['buy'] = true; | ||
console.log(tradeObject); | ||
|
||
this.bus.trigger('add_trade', tradeObject) | ||
this.model.buy(); | ||
} else { | ||
tradeObject['buy'] = false; | ||
this.bus.trigger('add_trade', tradeObject) | ||
this.model.sell(); | ||
} | ||
}, | ||
render() { | ||
let quote = this.model; | ||
// trigger quote_change event which openOrderlist view will listen for | ||
this.bus.trigger('quote_change', quote); | ||
console.log(this.model); | ||
const compiledTemplate = this.template(this.model.toJSON()); | ||
this.$el.html(compiledTemplate); | ||
return this; | ||
}, | ||
|
||
}); | ||
|
||
export default QuoteView; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import Backbone from 'backbone'; | ||
import Quote from '../models/quote'; | ||
|
||
const TradeListView = Backbone.View.extend({ | ||
initialize(params) { | ||
this.template = params.template; | ||
this.bus = params.bus; | ||
this.listenTo(this.bus, 'add_trade', this.render); | ||
}, | ||
render(tradeObject) { | ||
const compiledTemplate= this.template(tradeObject) | ||
this.$('#trades').prepend(compiledTemplate); | ||
}, | ||
}); | ||
|
||
export default TradeListView; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using an event on the bus to get the
quoteList
, why not pass it in as an extra parameter when instantiating theOpenOrderListView
? Building an event for it is overkill.