-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathoperation.coffee
1013 lines (738 loc) · 28.9 KB
/
operation.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
require './shared'
global.history = require './trade_history'
exchange = require './exchange'
crunch = require './crunch'
global.pusher = require('./pusher')
# Tracking lag
laggy = false
all_lag = []
LAGGY_THRESHOLD = 3
global.lag_logger = (lag) ->
all_lag.push Math.max 0, lag
laggy = all_lag.length == 0 || Math.max(lag) > LAGGY_THRESHOLD
#########################
# Main event loop
global.tick =
time: null
# lock: false
global.no_new_orders = false
process.on 'SIGINT', ->
bus.save {key: 'time_to_die', now: true}
log_tick = ->
time = fetch 'time'
extend time,
earliest: if time.earliest? then time.earliest else tick.started
latest: tick.time
bus.save time
unfilled_orders = ->
for dealer,positions of open_positions when positions.length > 0
for pos in positions
if (pos.entry && !pos.entry.closed) || (pos.exit && !pos.exit.closed)
return true
return false
one_tick = ->
# if tick.lock
# console.log "Skipping tick because it is still locked"
# return
tick.time = now()
time = fetch 'time'
# check if we need to tick
has_unfilled = unfilled_orders()
inc = if has_unfilled
pusher.tick_interval
else
pusher.tick_interval_no_unfilled
needs_to_run = tick.time - time.latest >= inc
if !needs_to_run && time.earliest?
return
if config.log_level > 1
console.log "TICKING @ #{tick.time}! #{all_lag.length} messages with #{Math.average(all_lag)} avg lag"
# tick.lock = true
history.load_price_data
start: (time.earliest or tick.started) - tick.history_to_keep
end: now()
callback: ->
if config.disabled
console.log 'disabled'
log_tick()
# tick.lock = false
KPI (stats) ->
m = stats.all.metrics
m.key = 'stats'
bus.save m
return
update_position_status ->
update_account_balances false, ->
setImmediate -> # free thread to process any last trades
history.last_trade = history.trades[0]
# tick.time = now()
if !laggy
balance = from_cache('balances')
pusher.reset_open()
pusher.hustle balance
# wait for the trades or cancelations to complete
i = setImmediate ->
for name in get_all_actors()
bus.save from_cache(name)
all_lag = []
log_tick()
console.log "Tick (mostly) done" if config.log_level > 1
KPI (stats) ->
m = stats.all.metrics
m.key = 'stats'
bus.save m
if config.reset_every && !unfilled_orders() && ( (tick.time - (tick.started or 0) > config.reset_every * 60 * 60) || global.restart_when_possible)
setTimeout ->
# write feature cache to disk
for resolution, engine of feature_engine.resolutions
engine.write_to_disk()
dealer_is_locked = false
for name in get_all_actors()
dealer = from_cache name
dealer_is_locked ||= dealer.locked
if !exchange.all_clear()
if config.log_level > 1
log_error false, {message: "Want to reset, but exchange has outstanding requests", time}
else if dealer_is_locked
if config.log_level > 1
console.error {message: "Want to reset, but at least one dealer is locked", time}
else
if config.log_level > 1
console.log 'Due for a reset. Assuming a monitoring process that will restart the app', {reset_every: config.reset_every, time: tick.time, started: tick.started, diff: tick.time - tick.started, should: (tick.time - (tick.started or 0) > config.reset_every * 60 * 60)}
bus.save {key: 'time_to_die', now: true}
, 100
# noww = now()
# history.disconnect_from_exchange_feed()
# history.load noww - tick.history_to_keep, noww, ->
# history.subscribe_to_exchange_feed ->
# tick.lock = false #...and now we're done with this tick
######
# Helpers
update_position_status = (callback) ->
time = from_cache 'time'
status = fetch 'position_status'
exchange.get_my_open_orders
c1: config.c1
c2: config.c2
, (all_open_orders) ->
if !all_open_orders || all_open_orders.error
console.error("Couldn't get open orders!", all_open_orders?.error)
callback?()
return
open_orders = {}
for trade in all_open_orders
open_orders[trade.order_id] = trade
if status.last_checked
start_checking_at = status.last_checked
else
# date of creation of earliest open trade
earliest = Infinity
for name in get_dealers()
for pos in from_cache(name).positions when !pos.closed
if pos.created < earliest
earliest = pos.created
if earliest == Infinity # no open trades to check
callback?()
return
start_checking_at = earliest
start_checking_at -= 60 * 1000 # request an extra minute of fills to be safe
check_to = now()
exchange.get_my_fills
c1: config.c1
c2: config.c2
start: start_checking_at
end: check_to
, (fills) ->
if !fills || fills.error
console.error("Couldn't get fills!", fills?.error)
callback?()
return
order2fills = {}
for fill in fills
order2fills[fill.order_id] ?= []
order2fills[fill.order_id].push fill
for name in get_dealers()
dealer_data = from_cache(name)
for pos in dealer_data.positions when !pos.closed
before = JSON.stringify(pos)
for t,idx in [pos.entry, pos.exit] when t && !t.closed && t.orders?.length > 0
t.fills = []
for order_id in t.orders when order_id
if order_id of order2fills
for new_fill in order2fills[order_id]
already_processed = false
for old_fill in t.fills
already_processed = old_fill.fill_id == new_fill.fill_id
break if already_processed
continue if already_processed
new_fill.type = t.type
t.fills.push new_fill
if t.fills.length > 0
if t.flags?.market && t.type == 'buy'
t.to_fill = t.total - Math.summation (f.total for f in t.fills)
else
t.to_fill = (t.original_amt or t.amount) - Math.summation (f.amount for f in t.fills)
if t.to_fill < 0
t.to_fill = 0
if dealer_data.locked
# log_error false, {message: "skipping status updating because dealer is locked", name}
bus.save pos
continue
orders_completed = t.orders.length > 0
for order_id in t.orders
orders_completed &&= !(order_id of open_orders)
if order_id != t.current_order && (order_id of open_orders)
if !t.current_order
t.current_order = order_id
else
log_error false,
message: "Old order not marked as completed!"
order: order_id
trade: t
if t.to_fill == 0 && !orders_completed
console.error {message: "Nothing to fill but orders not completed", trade: t, fills: t.fills, open_orders}
if orders_completed
if ( (t.flags?.market && t.type == 'buy') && t.to_fill > exchange.minimum_order_size(config.c1)) || \
( !(t.flags?.market && t.type == 'buy') && ( t.to_fill > exchange.minimum_order_size() && \
t.to_fill * t.rate > exchange.minimum_order_size(config.c1)) )
# Our filled amount doesn't match. We might have to issue another order. However, sometimes the exchange has marked an order
# as matched, but the fills aren't coming just yet. So we wait a little longer to make sure it is actually not completed yet.
key = JSON.stringify(t.orders)
t.assessing_clear_order_since ?= {}
t.assessing_clear_order_since[key] ||= tick.time
if tick.time - t.assessing_clear_order_since[key] > 5 * 60
delete t.assessing_clear_order_since[key]
# this trade still needs more fills
t.current_order = null
console.error
message: "#{config.exchange} thinks we\'ve completed the trade, but our filled amount does not match. We\'ll issue another order to make up the difference."
trade: t
fills: t.fills
to_fill: t.to_fill
pos: pos.key
was_locked: dealer_data.locked
if dealer_data.locked
dealer_data.locked = false
bus.save dealer_data
else
if !(t.fills?.length > 0)
log_error false, {message: "Trade is closing but had no fills", trade: t, fills: t.fills, pos}
close_trade(pos, t)
if pos.entry?.closed && pos.exit?.closed
buy = if pos.entry.type == 'buy' then pos.entry else pos.exit
sell = if pos.entry.type == 'buy' then pos.exit else pos.entry
pos.profit = (sell.total - buy.total - (sell.c1_fees + buy.c1_fees)) / \
pos.exit.rate + (buy.amount - sell.amount - (buy.c2_fees + sell.c2_fees))
pos.closed = Math.max pos.entry.closed, pos.exit.closed
changed = JSON.stringify(pos) != before
if changed || !pos.closed
if changed && config.log_level > 1
console.log 'CHANGED!', pos
bus.save pos
status.last_checked = check_to
save status
callback?()
############################
# Live trading interface
placed_order = ({result, pos, trade}) ->
trade = refresh pos, trade
if result.error
console.error "GOT ERROR TAKING OR MOVING POSITION", {pos, trade, err: result.error, body: result.body}
else if !result.order_id
result.error = "no order_id returned from placed order"
log_error true,
message: "no order_id returned from placed order"
result: result
pos: pos
trade: trade
else
new_order = result.order_id
trade.current_order = new_order
trade.first_order ?= tick.time
trade.latest_order = tick.time
trade.fills ?= []
trade.created ?= tick.now
trade.original_rate ?= trade.rate
trade.original_amt ?= trade.amount
trade.orders ?= []
if new_order not in trade.orders
trade.orders.push new_order
if result.info
trade.info ?= []
trade.info.push result.info
console.log "ADDED #{result.order_id} to trade", trade if config.log_level > 1
bus.save pos
place_dynamic_order = ({trade, amount, pos}, placed_callback) ->
console.log 'PLACING DYNAMIC ORDER' if config.log_level > 2
exchange.dynamic_place_order
type: trade.type
amount: amount
rate: trade.rate
c1: config.c1
c2: config.c2
flags: trade.flags
trade: trade # orphanable
pos: pos
, (result) -> # placed callback
# trade = refresh pos, trade
placed_order {result, pos, trade}
placed_callback result
if result.error
dealer = from_cache pos.dealer
dealer.locked = false
bus.save dealer
, (result) -> # updated callback
placed_order {result, pos, trade}
, -> # finished callback
console.log 'UNLOCKING DEALER' if config.log_level > 2
dealer = from_cache pos.dealer
dealer.locked = false
bus.save dealer
update_dynamic_order = ({trade, pos}) ->
console.log 'UPDATING DYNAMIC ORDER' if config.log_level > 2
exchange.dynamic_move_order
trade: trade # orphanable
pos: pos
, (result) -> # updated callback
placed_order {result, pos, trade}
, -> # finished callback
console.log 'UNLOCKING DEALER' if config.log_level > 2
dealer = from_cache pos.dealer
dealer.locked = false
bus.save dealer
take_position = (pos, callback) ->
dealer = from_cache(pos.dealer)
dealer.locked = tick.time
bus.save dealer
trades = (trade for trade in [pos.entry, pos.exit] when trade && !trade.current_order && !trade.closed)
trades_left = trades.length
for trade, idx in trades
if !(trade.amount?) || !(trade.rate?)
log_error true,
message: 'Ummmm, you have to give an amount and a rate to place a trade...'
trade: trade
return
error = false
for trade in trades
amount = trade.to_fill
do (trade, amount) ->
if amount < exchange.minimum_order_size()
log_error false,
message: "trying to place an order for less than minimum"
minimum: exchange.minimum_order_size()
trade: trade
pos: pos
return callback(true, trades_left)
if config.exchange == 'gdax' && trade.flags?.order_method > 1
place_dynamic_order
pos: pos
trade: trade
amount: amount
, (result) ->
trades_left--
error = error or result.error
console.log 'PLACED CALLBACK', {trades_left, error, trade, entry: pos.entry, exit: pos.exit} if config.log_level > 2
callback error, trades_left
else
console.log 'PLACING STATIC ORDER', {config, trade} if config.log_level > 2
exchange.place_order
type: trade.type
amount: amount
rate: trade.rate
c1: config.c1
c2: config.c2
flags: trade.flags
, (result) ->
trades_left--
placed_order {result, pos, trade}
error = error or result.error
if trades_left <= 0
dealer.locked = false
bus.save dealer
callback error, trades_left
update_trade = ({pos, trade, rate, amount}, callback) ->
trade = refresh pos, trade
console.assert !trade.flags?.market
dealer = from_cache(pos.dealer)
dealer.locked = tick.time
bus.save dealer
if config.log_level > 2
console.log 'UPDATING TRAAAAADE',
trade: trade
exchange: config.exchange
order_method: trade.flags?.order_method
if config.exchange == 'gdax' && trade.flags?.order_method > 1
if trade.current_order
update_dynamic_order
pos: pos
trade: trade
else
place_dynamic_order
pos: pos
trade: trade
amount: amount
, (result) ->
callback? result.error
else
cb = (result) ->
placed_order {result, pos, trade}
dealer.locked = false
bus.save dealer
callback? result.error
if trade.current_order
exchange.move_order
order_id: trade.current_order
rate: rate
amount: amount
type: trade.type
c1: config.c1
c2: config.c2
flags: trade.flags
, cb
else
exchange.place_order
type: trade.type
amount: amount
rate: rate
c1: config.c1
c2: config.c2
flags: trade.flags
, cb
cancel_unfilled = (pos, callback) ->
trades= (trade for trade in [pos.exit, pos.entry] when trade && !trade.closed && trade.current_order)
cancelations_left = trades.length
for trade in trades
do (trade) ->
exchange.cancel_order
order_id: trade.current_order
, (result) ->
trade = refresh pos, trade
cancelations_left--
if result?.error && result?.error != 'order not found'
console.log "GOT ERROR CANCELING POSITION", {pos, cancelations_left, err, resp, body}
else
console.log 'CANCELED POSITION'
delete trade.current_order
bus.save pos if pos.key
if cancelations_left == 0
callback()
bus.save pos
update_account_balances = (halt_on_error, callback) ->
sheet = from_cache 'balances'
initialized = false # sheet.balances?
dealers = get_dealers()
exchange.get_my_balance {c1: config.c1, c2: config.c2}, (result) ->
x_sheet =
balances: {}
on_order: {}
for currency, balance of result
if currency == config.c1
x_sheet.balances.c1 = parseFloat balance.available
x_sheet.on_order.c1 = parseFloat balance.on_order
else if currency == config.c2
x_sheet.balances.c2 = parseFloat balance.available
x_sheet.on_order.c2 = parseFloat balance.on_order
if !initialized
c1_deposit = config.c1_deposit or x_sheet.balances.c1
c2_deposit = config.c2_deposit or x_sheet.balances.c2
sheet.balances =
c1: c1_deposit
c2: c2_deposit
sheet.deposits =
c1: c1_deposit
c2: c2_deposit
sheet.on_order =
c1: 0
c2: 0
sheet.xchange_total =
c1: x_sheet.balances.c1 + x_sheet.on_order.c1
c2: x_sheet.balances.c2 + x_sheet.on_order.c2
# initialize, assuming equal distribution of available balance
# between dealers
num_dealers = dealers.length
for dealer in dealers
sheet[dealer] =
deposits:
c1: c1_deposit / num_dealers
c2: c2_deposit / num_dealers
balances:
c1: c1_deposit / num_dealers
c2: c2_deposit / num_dealers
on_order:
c1: 0
c2: 0
btc = eth = btc_on_order = eth_on_order = 0
for dealer in dealers
if !sheet[dealer]
log_error true,
message: 'dealer has no balance'
dealer: dealer
balance: sheet
positions = from_cache(dealer).positions
dbtc = deth = dbtc_on_order = deth_on_order = 0
for pos in positions
if pos.entry.type == 'buy'
buy = pos.entry
sell = pos.exit
else
buy = pos.exit
sell = pos.entry
if buy
# used or reserved for buying remaining eth
for_purchase = if buy.flags?.market then buy.to_fill else buy.to_fill * buy.rate
dbtc_on_order += for_purchase
dbtc -= for_purchase
if buy.fills?.length > 0
for fill in buy.fills
deth += fill.amount
dbtc -= fill.total
if config.exchange == 'poloniex'
deth -= fill.fee
else
dbtc -= fill.fee
if sell
for_sale = sell.to_fill
deth_on_order += for_sale
deth -= for_sale
if sell.fills?.length > 0
for fill in sell.fills
deth -= fill.amount
dbtc += fill.total
dbtc -= fill.fee
btc += dbtc
eth += deth
btc_on_order += dbtc_on_order
eth_on_order += deth_on_order
dbalance = sheet[dealer]
dbalance.balances.c1 = dbalance.deposits.c1 + dbtc
dbalance.balances.c2 = dbalance.deposits.c2 + deth
dbalance.on_order.c1 = dbtc_on_order
dbalance.on_order.c2 = deth_on_order
if dbalance.balances.c1 + dbtc_on_order < 0 || dbalance.balances.c2 + deth_on_order < 0
fills = []
fills_out = []
pos = null
for pos in positions
for trade in [pos.entry, pos.exit] when trade
fills = fills.concat trade.fills
console.log trade if config.log_level > 1
for fill in trade.fills
if fill.type == 'sell'
fills_out.push "#{pos.key}\t#{trade.entry or false}\t#{trade.created}\t#{fill.order_id}\t#{fill.total}\t-#{fill.amount}\t#{fill.fee}"
else
fills_out.push "#{pos.key}\t#{trade.entry or false}\t#{trade.created}\t#{fill.order_id}\t-#{fill.total}\t#{fill.amount}\t#{fill.fee}"
if !config.disabled
if pos.last_error != 'Negative balance'
log_error halt_on_error,
message: 'negative balance!?!'
dealer: dealer
balance: sheet.balances
dbalance: sheet[dealer]
positions: positions
last_entry: positions[positions.length - 1]?.entry
last_exit: positions[positions.length - 1]?.exit
fills: fills
fills_out: fills_out
else
console.error
message: 'negative balance!?!'
dealer: dealer
balance: sheet.balances
dbalance: sheet[dealer]
positions: positions
last_entry: positions[positions.length - 1]?.entry
last_exit: positions[positions.length - 1]?.exit
fills: fills
fills_out: fills_out
pos.last_error = 'Negative balance'
bus.save pos
sheet.balances.c1 = sheet.deposits.c1 + btc
sheet.balances.c2 = sheet.deposits.c2 + eth
sheet.on_order.c1 = btc_on_order
sheet.on_order.c2 = eth_on_order
if !config.disabled
if sheet.on_order.c1 != btc_on_order || sheet.on_order.c2 != eth_on_order
global.no_new_orders = true
log_error halt_on_error,
message: "Order amounts differ"
c1_on_order: btc_on_order
c2_on_order: eth_on_order
on_order: sheet.on_order
bus.save sheet
if !config.disabled
if sheet.deposits.c1 + btc > x_sheet.balances.c1 || sheet.deposits.c2 + eth > x_sheet.balances.c2
global.no_new_orders = true
log_error halt_on_error,
message: "Dealers have more balance than available"
c1: btc
c2: eth
diff_c1: x_sheet.balances.c1 - (sheet.deposits.c1 + btc)
diff_c2: x_sheet.balances.c2 - (sheet.deposits.c2 + eth)
deposits: sheet.deposits
exchange_balances: x_sheet.balances
callback?()
update_fee_schedule = (callback) ->
exchange.get_my_exchange_fee {}, (result) ->
balance = from_cache 'balances'
balance.maker_fee = result.maker_fee
balance.taker_fee = result.taker_fee
bus.save balance
callback()
migrate_data = ->
migrate = bus.fetch('migrations')
if !migrate.currency_specific_fee
console.warn 'MIGRATING FEES!'
for key, pos of bus.cache
if key.match('position/') && key.split('/').length == 2 && !pos.series_data
for trade in [pos.entry, pos.exit] when trade && trade.closed
# we'll reclose it to update info
close_trade(pos, trade)
migrate.currency_specific_fee = true
bus.save migrate
else if !migrate.profit_calcsss
console.warn 'MIGRATING profit_calc!'
for key, pos of bus.cache
if key.match('position/') && key.split('/').length == 2 && !pos.series_data && pos.closed && (pos.profit == null || !pos.profit? || isNaN(pos.profit))
buy = if pos.entry.type == 'buy' then pos.entry else pos.exit
sell = if pos.entry.type == 'buy' then pos.exit else pos.entry
pos.profit = (sell.total - buy.total - (sell.c1_fees + buy.c1_fees)) / \
pos.exit.rate + (buy.amount - sell.amount - (buy.c2_fees + sell.c2_fees))
console.log pos
bus.save pos
migrate.profit_calcsss = true
bus.save migrate
operation = module.exports =
start: (conf) ->
global.config = fetch 'config'
global.config = defaults {}, conf, config,
exchange: 'poloniex'
simulation: false
eval_entry_every_n_seconds: 60
eval_exit_every_n_seconds: 60
eval_unfilled_every_n_seconds: 60
c1: 'BTC'
c2: 'ETH'
accounting_currency: 'USDT'
enforce_balance: true
log_level: 1
bus.save config
migrate_data()
time = from_cache 'time'
console.log 'STARTING METH' if config.log_level > 1
console.error 'STARTING METH' if config.log_level > 1
bus.save {key: 'time_to_die', now: false}
bus('time_to_die').on_save = (o) ->
if o.now
setTimeout process.exit, 1
bus('die die die').on_save = (o) ->
global.restart_when_possible = true
if config.disabled
console.log "This operation is abandoned. Nothing to see here."
return
pusher.init {history, take_position, cancel_unfilled, update_trade}
# load feature cache to disk
for resolution, engine of feature_engine.resolutions
engine.load_from_disk()
history_width = history.longest_requested_history
console.log "...loading past #{(history_width / 60 / 60 / 24).toFixed(2)} days of trade history" if config.log_level > 1
tick.started = ts = now()
tick.history_to_keep = history_width
for name in get_all_actors()
dealer = from_cache name
if dealer.locked
dealer.locked = false
bus.save dealer
console.error {message: "Unlocked dealer on reset. Could have bad state.", name}
history.load_price_data
start: (time.earliest or tick.started) - tick.history_to_keep
end: ts
callback: ->
history.load ts - history_width, ts, ->
history.last_trade = history.trades[0]
console.log "...connecting to #{config.exchange} live updates" if config.log_level > 1
history.subscribe_to_exchange_feed ->
console.log "...updating account balances" if config.log_level > 1
update_fee_schedule ->
update_position_status ->
update_account_balances true, ->
console.log "...hustling!" if config.log_level > 1
one_tick()
setInterval one_tick, pusher.tick_interval * 1000
setup: ({port, db_name, clear_old}) ->
global.pointerify = true
global.upload_dir = 'static/'
global.bus = require('statebus').serve
port: port
file_store: false
client: false
bus.honk = false
if clear_old && fs.existsSync(db_name)
console.log 'Destroying old db'
fs.unlinkSync(db_name)
bus.sqlite_store
filename: db_name
use_transactions: false
global.save = bus.save
global.fetch = (key) ->
bus.fetch deslash key
global.del = bus.del
#save key: 'operation'
require 'coffee-script'
require './shared'
express = require('express')
bus.http.use('/node_modules', express.static('node_modules'))
bus.http.use('/node_modules', express.static('meth/node_modules'))
bus.http.use('/meth/vendor', express.static('meth/vendor'))
# taken from statebus server.js. Just wanted different client path.
bus.http_serve '/meth/:filename', (filename) ->
filename = deslash filename
source = bus.read_file(filename)
if filename.match(/\.coffee$/)
try
compiled = require('coffee-script').compile source,
filename: filename
bare: true
sourceMap: true
catch e
console.error('Could not compile ' + filename + ': ', e)
return ''
compiled = require('coffee-script').compile(source, {filename: filename, bare: true, sourceMap: true})
source_map = JSON.parse(compiled.v3SourceMap)
source_map.sourcesContent = source
compiled = 'window.dom = window.dom || {}\n' + compiled.js
compiled = 'window.ui = window.ui || {}\n' + compiled
btoa = (s) -> return new Buffer(s.toString(),'binary').toString('base64')
# Base64 encode it
compiled += '\n'
compiled += '//# sourceMappingURL=data:application/json;base64,'
compiled += btoa(JSON.stringify(source_map)) + '\n'
compiled += '//# sourceURL=' + filename
return compiled
else return source
bus.http.get '/*', (r,res) =>
paths = r.url.split('/')
paths.shift() if paths[0] == ''
prefix = ''
server = "statei://#{get_ip_address()}:#{port}"
html = """
<!DOCTYPE html>
<html>
<head>
<script type="coffeedom">
bus.honk = false
#</script>
<script src="#{prefix}/node_modules/statebus/client.js" server="#{server}"></script>
<script src="#{prefix}/meth/vendor/d3.js"></script>
<script src="#{prefix}/meth/vendor/md5.js"></script>
<script src="#{prefix}/meth/vendor/plotly.js"></script>
<script src="#{prefix}/meth/shared.coffee"></script>
<script src="#{prefix}/meth/crunch.coffee"></script>
<script src="#{prefix}/meth/dash.coffee"></script>