1+ import BigNumber from "bignumber.js" ;
2+ import { EventEmitter } from 'events' ;
3+ import moment from "moment" ;
4+ import { RestClient } from "./restClient" ;
5+ import { Config , LoadableValue , Market , Notification , Order , OrderBook , Side , Token } from "./types" ;
6+ const exchange = require ( './sign/exchange.js' )
7+
8+ export declare interface MarketState {
9+ on ( event : 'maxBidChanged' , listener : ( maxBid : BigNumber | undefined ) => void ) : this;
10+ on ( event : 'minAskChanged' , listener : ( minAsk : BigNumber | undefined ) => void ) : this;
11+ on ( event : 'baseTokenUnallocatedChanged' , listener : ( unallocated : BigNumber ) => void ) : this;
12+ on ( event : 'quoteTokenUnallocatedChanged' , listener : ( unallocated : BigNumber ) => void ) : this;
13+ on ( event : string , listener : Function ) : this;
14+ }
15+
16+ export class MarketState extends EventEmitter {
17+ private _restClient : RestClient ;
18+ private _market : Market ;
19+ private _orderBook : OrderBook | undefined ;
20+ private _lastOrderBookVersion : number | undefined ;
21+ private _maxBid : BigNumber | undefined ;
22+ private _minAsk : BigNumber | undefined ;
23+ private _openOrders : any ;
24+
25+ private _config : Config ;
26+
27+ private baseTokenUnit : BigNumber ;
28+ private quoteTokenUnit : BigNumber ;
29+
30+ private _initialized : boolean ;
31+
32+ public baseTokenUnallocated : LoadableValue < BigNumber > ;
33+ public quoteTokenUnallocated : LoadableValue < BigNumber > ;
34+ public nextStorageIdbaseToken : LoadableValue < number > ;
35+ public nextStorageIdquoteToken : LoadableValue < number > ;
36+
37+ readonly maxBuyPrice : BigNumber ;
38+ readonly minSellPrice : BigNumber ;
39+ readonly baseToken : Token ;
40+ readonly quoteToken : Token ;
41+
42+ constructor ( market : Market , baseToken : Token , quoteToken : Token , config : Config , restClient : RestClient ) {
43+ super ( ) ;
44+ this . _config = config ;
45+
46+ this . _restClient = restClient ;
47+ this . _market = market ;
48+ this . maxBuyPrice = new BigNumber ( config . maxBuyPrice ) ;
49+ this . minSellPrice = new BigNumber ( config . minSellPrice ) ;
50+ this . baseTokenUnallocated = new LoadableValue < BigNumber > ( ) ;
51+ this . quoteTokenUnallocated = new LoadableValue < BigNumber > ( ) ;
52+ this . nextStorageIdbaseToken = new LoadableValue < number > ( ) ;
53+ this . nextStorageIdquoteToken = new LoadableValue < number > ( ) ;
54+
55+ this . baseToken = baseToken ;
56+ this . quoteToken = quoteToken ;
57+
58+ this . baseTokenUnit = new BigNumber ( 10 ) . exponentiatedBy ( baseToken . decimals )
59+ this . quoteTokenUnit = new BigNumber ( 10 ) . exponentiatedBy ( quoteToken . decimals )
60+
61+ this . _initialized = false ;
62+
63+ if ( this . maxBuyPrice . isNaN ( ) || this . minSellPrice . isNaN ( ) ) {
64+ console . error ( 'maxBuyPrice and minSellPrice MUST be configured.' )
65+ process . exit ( )
66+ }
67+ }
68+
69+ get market ( ) : Market {
70+ return this . _market
71+ }
72+
73+ get orderBook ( ) : OrderBook | undefined {
74+ return this . _orderBook ;
75+ }
76+
77+ get minAsk ( ) : BigNumber | undefined {
78+ return this . _minAsk ;
79+ }
80+
81+ get maxBid ( ) : BigNumber | undefined {
82+ return this . _maxBid ;
83+ }
84+
85+ updateOrderBook ( version : number , orderbook : OrderBook | undefined ) {
86+ if ( orderbook && ( ! this . _lastOrderBookVersion || version > this . _lastOrderBookVersion ) ) {
87+ this . _orderBook = orderbook ;
88+
89+ let __maxBid : string | undefined = undefined ;
90+ let __minAsk : string | undefined = undefined ;
91+ if ( orderbook . bids . length > 0 ) __maxBid = orderbook . bids [ 0 ] [ 0 ]
92+ if ( orderbook . asks . length > 0 ) __minAsk = orderbook . asks [ 0 ] [ 0 ]
93+
94+ if ( ( __maxBid && this . _maxBid && ! this . _maxBid . isEqualTo ( __maxBid ) ) ||
95+ ( ! __maxBid && this . _maxBid ) ||
96+ ( __maxBid && ! this . _maxBid ) ) {
97+ this . _maxBid = __maxBid !== undefined ? new BigNumber ( __maxBid ) : undefined
98+ this . emit ( 'maxBidChanged' , __maxBid ) ;
99+ }
100+
101+ if ( ( __minAsk && this . _minAsk && ! this . _minAsk . isEqualTo ( __minAsk ) ) ||
102+ ( ! __minAsk && this . _minAsk ) ||
103+ ( __minAsk && ! this . _minAsk ) ) {
104+ this . _minAsk = __minAsk !== undefined ? new BigNumber ( __minAsk ) : undefined
105+ this . emit ( 'minAskChanged' , __minAsk ) ;
106+ }
107+ }
108+ }
109+
110+ get openOrders ( ) : any {
111+ return this . _openOrders ;
112+ }
113+
114+ set openOrders ( oo : any ) {
115+ this . _openOrders = oo ;
116+ }
117+
118+ updateUnallocatedBalance ( tokenId : number , total : BigNumber . Value , locked : BigNumber . Value ) {
119+ const unallocated = new BigNumber ( total ) . minus ( locked ) ;
120+ if ( tokenId === this . baseToken . tokenId ) {
121+ this . baseTokenUnallocated . set ( unallocated )
122+ this . emit ( 'baseTokenUnallocatedChanged' , unallocated ) ;
123+ } else if ( tokenId === this . quoteToken . tokenId ) {
124+ this . quoteTokenUnallocated . set ( unallocated )
125+ this . emit ( 'quoteTokenUnallocatedChanged' , unallocated ) ;
126+ }
127+ }
128+
129+ updateStorageId ( tokenId : number , storageData : any ) {
130+ if ( storageData ?. orderId ) {
131+ if ( tokenId === this . baseToken . tokenId )
132+ this . nextStorageIdbaseToken . set ( storageData . orderId )
133+ else if ( tokenId === this . quoteToken . tokenId )
134+ this . nextStorageIdquoteToken . set ( storageData . orderId )
135+ console . log ( `nextStorageId for ${ tokenId } updated (${ storageData . orderId } )` )
136+ } else {
137+ if ( tokenId === this . baseToken . tokenId ) {
138+ this . nextStorageIdbaseToken . unset ( )
139+ } else if ( tokenId === this . quoteToken . tokenId ) {
140+ this . nextStorageIdquoteToken . unset ( )
141+ }
142+ }
143+ }
144+
145+ getCounterpartAmount ( amount : BigNumber , price : BigNumber , type : Side ) : string {
146+ if ( type === Side . Buy ) {
147+ let p = amount . dividedBy ( this . quoteTokenUnit ) ;
148+ let t = p . dividedBy ( price ) ;
149+ let r = t . multipliedBy ( this . baseTokenUnit ) . toFixed ( 0 ) ;
150+ return r ;
151+ } else {
152+ let p = amount . dividedBy ( this . baseTokenUnit ) ;
153+ let t = p . multipliedBy ( price ) ;
154+ let r = t . multipliedBy ( this . quoteTokenUnit ) . toFixed ( 0 ) ;
155+ return r ;
156+ }
157+ }
158+
159+ initialize ( ) {
160+ this . _initialized = false ;
161+ this . updateBaseTokenStorageId ( )
162+ this . updateQuoteTokenStorageId ( )
163+ this . updateBalances ( )
164+ this . updateOpenOrders ( )
165+ }
166+
167+ updateBaseTokenStorageId ( ) {
168+ this . nextStorageIdbaseToken . update ( async ( ) => {
169+ return this . _restClient . getStorageId ( this . baseToken . tokenId )
170+ } ) . then ( s => { console . log ( `baseToken StorageId updated (${ s } )` ) } )
171+ }
172+
173+ updateQuoteTokenStorageId ( ) {
174+ this . nextStorageIdquoteToken . update ( async ( ) => {
175+ return this . _restClient . getStorageId ( this . quoteToken . tokenId )
176+ } ) . then ( s => { console . log ( `quoteToken StorageId updated (${ s } )` ) } )
177+ }
178+
179+ updateBalances ( ) {
180+ this . _restClient . getBalances ( [ this . baseToken . tokenId , this . quoteToken . tokenId ] )
181+ . then ( ( obj : any ) => {
182+ obj . forEach ( ( bal : { tokenId : any ; total : any ; locked : any } ) => {
183+ this . updateUnallocatedBalance ( bal . tokenId , bal . total , bal . locked )
184+ } ) ;
185+ } )
186+ . catch ( err => {
187+ console . error ( 'error updating balances' , err ) ;
188+ this . quoteTokenUnallocated . unset ( ) ;
189+ this . baseTokenUnallocated . unset ( ) ;
190+ } )
191+ }
192+
193+ updateOpenOrders ( ) {
194+ this . _restClient . getOpenOrders ( this . market )
195+ . then ( ( obj : any ) => {
196+ this . openOrders = obj . orders ;
197+ console . log ( `openOrders loaded (${ this . openOrders . length } )` ) ;
198+ } )
199+ . catch ( err => {
200+ console . error ( 'error getting open orders' , err ) ;
201+ this . openOrders = undefined ;
202+ } )
203+ }
204+
205+ consumeNotification ( notification : Notification ) {
206+ var topic = notification . topic . topic ;
207+ var data = notification . data ;
208+
209+ switch ( topic ) {
210+ case 'account' :
211+ this . updateUnallocatedBalance ( data . tokenId , data . totalAmount , data . amountLocked )
212+ break ;
213+ case 'orderbook' :
214+ if ( this . market . market === notification . topic . market )
215+ this . updateOrderBook ( notification . endVersion , data ) ;
216+
217+ break ;
218+ }
219+ }
220+
221+ prepareNewOrder ( amount : BigNumber , price : BigNumber , type : Side ) {
222+ let storageId : number ;
223+
224+ storageId = type === Side . Buy ? this . nextStorageIdquoteToken . value : this . nextStorageIdbaseToken . value ;
225+
226+ return this . prepareOrder ( storageId , amount , price , type ) ;
227+ }
228+
229+ prepareUpdateOrder ( storageId : number , amount : BigNumber , price : BigNumber , type : Side ) {
230+ // TODO
231+ return this . prepareOrder ( storageId , amount , price , type ) ;
232+ }
233+
234+ private prepareOrder ( storageId : number , amount : BigNumber , price : BigNumber , type : Side ) : Order | undefined {
235+ let sellTokenId : string ;
236+ let sellTokenVolume : string ;
237+ let buyTokenId : string ;
238+ let buyTokenVolume : string ;
239+
240+ if ( type === Side . Buy ) {
241+ buyTokenId = String ( this . baseToken . tokenId ) ;
242+ sellTokenId = String ( this . quoteToken . tokenId ) ;
243+ if ( amount . isGreaterThan ( this . quoteTokenUnallocated . value ) ) {
244+ console . error ( 'trying to use more than avaibable amount' )
245+ return undefined ;
246+ }
247+ sellTokenVolume = this . quoteTokenUnallocated . value . toFixed ( ) ;
248+ buyTokenVolume = this . getCounterpartAmount ( amount , price , type )
249+ } else {
250+ buyTokenId = String ( this . quoteToken . tokenId ) ;
251+ sellTokenId = String ( this . baseToken . tokenId ) ;
252+ if ( amount . isGreaterThan ( this . baseTokenUnallocated . value ) ) {
253+ console . error ( 'trying to use more than avaibable amount' )
254+ return undefined ;
255+ }
256+ sellTokenVolume = this . baseTokenUnallocated . value . toFixed ( ) ;
257+ buyTokenVolume = this . getCounterpartAmount ( amount , price , type )
258+ }
259+
260+ let order : Order = {
261+ "exchange" : this . _config . account . exchangeAddress ,
262+ "accountId" : this . _config . account . accountId ,
263+ "storageId" : storageId ,
264+ "sellToken" : {
265+ "tokenId" : sellTokenId ,
266+ "volume" : sellTokenVolume
267+ } ,
268+ "buyToken" : {
269+ "tokenId" : buyTokenId ,
270+ "volume" : buyTokenVolume
271+ } ,
272+ "allOrNone" : false ,
273+ "fillAmountBOrS" : type === Side . Buy ,
274+ "validUntil" : moment ( ) . add ( 2 , 'month' ) . utc ( ) . unix ( ) ,
275+ "maxFeeBips" : 50 ,
276+ "orderType" : "MAKER_ONLY"
277+ }
278+
279+
280+ return exchange . signOrder ( order ,
281+ {
282+ secretKey : this . _config . account . privateKey ,
283+ publicKeyX : this . _config . account . publicKeyX ,
284+ publicKeyY : this . _config . account . publicKeyY
285+ } , this . baseToken , this . quoteToken ) ;
286+ }
287+
288+ get initialized ( ) : boolean {
289+ if ( this . _initialized ) return true ;
290+
291+ if ( this . nextStorageIdbaseToken . isAvailable &&
292+ this . nextStorageIdbaseToken . isAvailable &&
293+ this . baseTokenUnallocated . isAvailable &&
294+ this . quoteTokenUnallocated . isAvailable ) {
295+
296+ this . _initialized = true
297+ return true
298+ }
299+
300+ return false ;
301+ }
302+ }
0 commit comments