/
market.coffee
138 lines (120 loc) · 4.36 KB
/
market.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
{inspect} = require 'util'
Stream = require 'stream'
{delay} = require 'ragtime'
geekdaq = require 'geekdaq'
pretty = (obj) -> "#{inspect obj, no, 20, yes}"
randInt = (min,max) -> Math.round(min + Math.random() * (max - min))
isString = (obj) -> !!(obj is '' or (obj and obj.charCodeAt and obj.substr))
log = console.log
class module.exports extends Stream
constructor: (options={}) ->
@server = options.server ? 'geekdaq'
@codes = options.tickers ? []
@updateInterval = options.updateInterval ? 500
@commissions = options.commissions ? {buy: 0, sell: 0}
# pre-defined accoutns loader (accept either a map or an array)
accounts = options.accounts ? []
@accounts = {}
if Array.isArray accounts
for account in accounts
@accounts[account.username] = account
else
for k,v of accounts
@accounts[k] = v
@tickers = {}
for code in @codes
@tickers[code] =
func: geekdaq.generator
range: 5
levels: 150
price: randInt 100, 400
volume: randInt 100000, 400000
@running = no
# stock market data update frequency
update: =>
if @running
for code, ticker of @tickers
ticker.price += ticker.func()
delay @updateInterval, =>
@update()
else
@emit 'stopped'
stop: =>
@emit 'stopping'
if @running
@running = no
return
@emit 'stopped'
start: =>
@emit 'starting'
if @running
@emit 'started'
return
@running = yes
@emit 'started'
@update()
ticker: (t) =>
if isString(t) then @tickers[t] else t
register: (account) =>
@accounts[account.username] = account
@emit 'registration', account.username
transfert: (username, amount, origin) =>
@accounts[username].balance += amount
if origin?
@accounts[origin].balance -= amount
@emit 'transfert', username, amount, origin
else
@emit 'transfert', username, amount
execute: ({username, orders, onComplete}) =>
account = @accounts[username]
@emit 'debug', "username: #{username} and account: #{pretty account}"
for order in orders
@emit 'debug', "going to #{order.type} #{order.amount} #{order.ticker}:"
ticker = @ticker order.ticker
switch order.type
when 'buy'
raw_cost = order.amount * ticker.price
#puts "raw cost: #{raw_cost}"
total_cost = raw_cost + (raw_cost * @commissions.buy) # commission
@emit 'debug', "buy total cost: #{total_cost}"
if account.balance < total_cost
msg = "#{username}'s balance is #{account.balance}, but cost is #{total_cost}"
@emit 'error', 'NOT_ENOUGH_MONEY', msg
else
account.balance -= total_cost
#log "order executed, balance is now #{worker.balance}"
if order.symbol of account.portfolio
account.portfolio[order.ticker] += order.amount
else
account.portfolio[order.ticker] = order.amount
account.history.push
type: order.type
ticker: order.ticker
amount: order.amount
price: ticker.price
expenses: total_cost
when 'sell'
unless order.ticker of account.portfolio
msg = "#{username} doesn't own any #{order.ticker}"
@emit 'error', 'NOT_IN_PORTFOLIO', msg
else
amount = account.portfolio[order.ticker]
if amount < order.amount
msg = "#{username} doesn't have enough #{order.ticker} to sell (want to sell #{order.amount}, but we have #{amount})"
@emit 'error', 'NOT_ENOUGH_SHARES', msg
else
raw_earnings = amount * ticker.price
total_earnings = raw_earnings - (raw_earnings * @commissions.sell)
@emit 'debug', "total earnings: #{total_earnings}"
account.portfolio[order.ticker] -= order.amount
account.balance += total_earnings
account.history.push
type: order.type
ticker: order.ticker
amount: order.amount
price: ticker.price
earnings: total_earnings
else
msg = "unknown order type '#{order.type}'"
@emit 'error', 'UNKNOWN_ORDER_TYPE', msg
onComplete undefined