Skip to content

Commit a843645

Browse files
committed
Added trade symbol validation
1 parent d582c52 commit a843645

11 files changed

+163
-49
lines changed

API/APIServer.py

+52-27
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,30 @@
1-
from flask import Flask, Response
1+
from flask import Flask, Response, app, render_template
22
from flask_restful import reqparse, abort, Api, Resource
33
from flask_cors import CORS
44
import json
55

66
from API.APIResult import APIResult
7+
from Bot.ExchangeInfo import ExchangeInfo
78
from Bot.ConfigLoader import ConfigLoader
89
from Bot.TradeHandler import TradeHandler
910
from Utils.Logger import Logger
1011

1112

13+
app = Flask(__name__, static_folder='templates', static_url_path='')
14+
15+
@app.route('/')
16+
@app.route('/trades')
17+
def index():
18+
return render_template('index.html')
19+
20+
1221
class APIServer:
1322
VERSION = 'v1'
1423
API_PREFIX = '/api/'+VERSION
1524

1625
def __init__(self, trade_handler: TradeHandler):
1726
self.th = trade_handler
18-
self.app = Flask(__name__)
27+
self.app = app
1928
self.api = Api(self.app)
2029
CORS(self.app)
2130

@@ -25,12 +34,14 @@ def __init__(self, trade_handler: TradeHandler):
2534
resource_class_kwargs={'trade_handler': self.th})
2635
self.api.add_resource(Trade, APIServer.API_PREFIX + '/trade/<id>',
2736
resource_class_kwargs={'trade_handler': self.th})
28-
self.api.add_resource(Managment, APIServer.API_PREFIX + '/management/<action>',
29-
resource_class_kwargs={'trade_handler': self.th})
37+
# self.api.add_resource(Managment, APIServer.API_PREFIX + '/management/<action>',
38+
# resource_class_kwargs={'trade_handler': self.th})
3039

40+
self.api.add_resource(APIExchangeInfo, APIServer.API_PREFIX + '/info',
41+
resource_class_kwargs={'trade_handler': self.th})
3142

3243
def run(self, port):
33-
self.app.run(debug=True, port=port, use_reloader=False)
44+
self.app.run(host='0.0.0.0', debug=True, port=port, use_reloader=False)
3445

3546

3647
class BotAPIREsource(Resource, Logger):
@@ -40,6 +51,21 @@ def __init__(self, trade_handler: TradeHandler):
4051
self.th: TradeHandler = trade_handler
4152

4253

54+
class APIExchangeInfo(BotAPIREsource):
55+
def get(self):
56+
return list(ExchangeInfo().get_all_symbols())
57+
58+
def post(self):
59+
args = self.parser.parse_args()
60+
action = args['action']
61+
62+
if not action:
63+
return APIResult.ErrorResult(100, msg='No "action" was provided'), 403
64+
65+
if action == 'reconnect':
66+
self.th.force_reconnect_sockets()
67+
68+
4369
class TradeList(BotAPIREsource):
4470
def get(self):
4571
return [{
@@ -70,8 +96,8 @@ def delete(self, id):
7096
if not strategies:
7197
return APIResult.ErrorResult(101, msg='No strategies were found'), 404
7298

73-
# for strategy in strategies:
74-
# self.th.remove_trade_by_strategy(strategy, True)
99+
for strategy in strategies:
100+
self.th.remove_trade_by_strategy(strategy, True)
75101

76102
return APIResult.OKResult()
77103

@@ -119,27 +145,26 @@ def post(self, id=None):
119145
self.th.updated_trade(trade)
120146
self.th.fire_trade_updated(trade, True)
121147
except Exception as e:
122-
# return Response(APIResult.ErrorResult(100, e), 500, mimetype='application/json')
123148
return json.dumps(APIResult.ErrorResult(100, str(e))), 500
124149

125150
return APIResult.OKResult(ids), 201
126151

127-
return APIResult.OKResult()
128-
129-
class Managment(BotAPIREsource):
130-
def post(self, action):
131-
if not action:
132-
return 'action should be \'pause\' or \'start\''
133-
134-
action = action.lower()
135-
136-
if action == 'pause':
137-
self.th.pause()
138-
139-
elif action == 'start':
140-
self.th.resume()
141-
142-
elif action == 'cancel':
143-
pass
144-
145-
return {}
152+
return APIResult.OKResult(), 200
153+
154+
# class Managment(BotAPIREsource):
155+
# def post(self, action):
156+
# if not action:
157+
# return 'action should be \'pause\' or \'start\''
158+
#
159+
# action = action.lower()
160+
#
161+
# if action == 'pause':
162+
# self.th.pause()
163+
#
164+
# elif action == 'start':
165+
# self.th.resume()
166+
#
167+
# elif action == 'cancel':
168+
# pass
169+
#
170+
# return {}

Bot/Exchange/Binance/BinanceWebsocket.py

+2
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ async def management_loop(self):
101101
self.start_ticker()
102102

103103
await asyncio.sleep(60)
104+
except asyncio.CancelledError:
105+
pass
104106
except:
105107
self.logError(traceback.format_exc())
106108

Bot/ExchangeInfo.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,7 @@ def has_symbol(self, symbol):
6060
return symbol in self.symbols
6161

6262
def has_all_symbol(self, symbols):
63-
return set(self.symbols) <= self.symbols
63+
return set(symbols) <= self.symbols
64+
65+
def get_all_symbols(self):
66+
return [{'s': s['symbol'], 'b': s['baseAsset']} for s in self.exchnage_info['symbols']]

Bot/TradeHandler.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -232,9 +232,11 @@ def remove_trade_by_strategy(self, strategy, api_call=False):
232232
finally:
233233
self.start_listening()
234234

235-
# def remove_trade_by_symbol(self, sym):
236-
# with self.lock:
237-
# self.remove_trade_by_strategy(self.strategies_dict.get(sym, None))
235+
def force_reconnect_sockets(self):
236+
with self.lock:
237+
self.stop_listening()
238+
self.fx.listen_symbols([s.symbol() for s in self.strategies], self.listen_handler, self.user_data_handler)
239+
self.start_listening()
238240

239241
def get_strategy_by_id(self, id) -> TradingStrategy:
240242
return self.tradeid_strategy_dict.get(id, None)

admin/src/app/apiresult.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export class ApiResult {
2-
status: number;
3-
msg: string;
2+
status?: number;
3+
msg?: string;
44
}

admin/src/app/app.module.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {AlertModule, ButtonsModule, CollapseModule, ModalModule, TooltipModule} from 'ngx-bootstrap';
1+
import {AlertModule, ButtonsModule, CollapseModule, ModalModule, TooltipModule, TypeaheadModule} from 'ngx-bootstrap';
22
import {BrowserModule} from '@angular/platform-browser';
33
import {CUSTOM_ELEMENTS_SCHEMA, NgModule} from '@angular/core';
44

@@ -11,6 +11,7 @@ import {TradesRoutingModule} from './trades-routing.module';
1111
import {FormsModule} from '@angular/forms';
1212
import {ExitDetailsComponent} from './trade-details/exit-details.component';
1313
import {SLDetailsComponent} from './trade-details/sl-details.component';
14+
import {SymbolValidatorDirective} from './trade-details/symbol-validator';
1415

1516
// const schemas: any[] = [];
1617
// schemas.push(CUSTOM_ELEMENTS_SCHEMA);
@@ -21,10 +22,12 @@ import {SLDetailsComponent} from './trade-details/sl-details.component';
2122
AssetTableComponent,
2223
TradeDetailsComponent,
2324
ExitDetailsComponent,
24-
SLDetailsComponent
25+
SLDetailsComponent,
26+
SymbolValidatorDirective
2527
],
2628
imports: [
2729
// CollapseModule.forRoot(),
30+
TypeaheadModule.forRoot(),
2831
ButtonsModule.forRoot(),
2932
ModalModule.forRoot(),
3033
AlertModule.forRoot(),

admin/src/app/botapi.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ export class BotApi {
2626
deleteProperty(sanitizedTrade, '_stoploss');
2727

2828
// console.log(o);
29-
return this.http.post<ApiResult>(`${this.API_URL}/trade/0`, JSON.stringify({action: 'add', data: {trade: sanitizedTrade} }), httpOptions).pipe(
29+
return this.http.post<ApiResult>(`${this.API_URL}/trade/0`,
30+
JSON.stringify({action: 'add', data: {trade: sanitizedTrade} }),
31+
httpOptions).pipe(
3032
// tap(_ => this.log(`Trade ${id} is closed`)),
3133
catchError(this.handleError('closeTrade'))
3234
);
@@ -49,6 +51,14 @@ export class BotApi {
4951
);
5052
}
5153

54+
getExchangeInfo(): Observable<any> {
55+
return this.http.get<any>(`${this.API_URL}/info`).pipe(
56+
retry(this.RETRIES),
57+
// tap(trades => this.log(`fetched trades`)),
58+
catchError(this.handleError('getExchangeInfo', []))
59+
);
60+
}
61+
5262
// private handleError<T>(error: HttpErrorResponse, def: T[]): Observable<T[]> {
5363
// if (error.error instanceof ErrorEvent) {
5464
// // A client-side or network error occurred. Handle it accordingly.
@@ -123,7 +133,7 @@ export class BotApi {
123133
* @param operation - name of the operation that failed
124134
* @param result - optional value to return as the observable result
125135
*/
126-
private handleError<T> (operation = 'operation', result?: T) {
136+
private handleError<T> (operation, result?: T) {
127137
return (error: any): Observable<T> => {
128138

129139
// TODO: send the error to remote logging infrastructure

admin/src/app/trade-details.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
import {st} from '@angular/core/src/render3';
2+
13
export class TradeDetails {
24

35
private _stoploss: StopLoss;
46

57
id: string;
68
asset: string;
9+
cap: string;
710
symbol: string = '';
8-
side: 'BUY' | 'SELL';
11+
side: 'BUY' | 'SELL' = 'SELL';
912
status: 'NEW' | 'ACTIVE' | 'COMPLETED';
1013
exit: ExitInfo;
1114

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Directive, Input, OnChanges, SimpleChanges } from '@angular/core';
2+
import { AbstractControl, NG_VALIDATORS, Validator, ValidatorFn, Validators } from '@angular/forms';
3+
4+
@Directive({
5+
selector: '[appSymbolValidator]',
6+
providers: [{provide: NG_VALIDATORS, useExisting: SymbolValidatorDirective, multi: true}]
7+
})
8+
export class SymbolValidatorDirective implements Validator {
9+
@Input('appSymbolValidator')
10+
appSymbolValidator: Set<string>;
11+
12+
validate(control: AbstractControl): {[key: string]: any} | null {
13+
// return this.appSymbolValidator ? symbolValidator(new RegExp(this.appSymbolValidator, 'i'))(control)
14+
// : null;
15+
if (this.appSymbolValidator) {
16+
return this.appSymbolValidator.has(control.value) ? null : {'appSymbolValidator': {value: control.value}};
17+
}
18+
return null;
19+
}
20+
}

admin/src/app/trade-details/trade-details.component.html

+37-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<ng-template #modal_template >
2+
<form #tradeForm="ngForm" class="needs-validation" novalidate>
23
<div class="modal-header">
34
<div class="col-sm-1">
45
<button *ngIf="mode.isView()" type="button" class="btn btn-sm btn-warning" (click)="mode.setEdit()" tooltip="Edit">
@@ -20,19 +21,48 @@ <h4 class="modal-title pull-left">Trade ({{mode.str()}} mode)</h4>
2021
<fieldset [disabled]="!mode.isCreate()">
2122
<legend>Info</legend>
2223
<div class="form-group row">
23-
<label for="tradeId" class="col-sm-2 col-form-label">ID</label>
24-
<div class="col-sm-10"><input type="text" id="tradeId" class="form-control" disabled value="{{trade.id}}"></div>
24+
<label for="tradeId" class="col-sm-2 col-form-label-sm">ID</label>
25+
<div class="col-sm-10"><input type="text" id="tradeId" name="tradeId" class="form-control form-control-sm" disabled value="{{trade.id}}"></div>
2526
</div>
2627
<div class="form-group row">
27-
<label for="symbol" class="col-sm-2 col-form-label">Symbol</label>
28-
<div class="col-sm-10"><input type="text" id="symbol" class="form-control" [(value)]="trade.symbol" ></div>
28+
<label for="symbol" class="col-sm-2 col-form-label-sm">Symbol</label>
29+
<div class="col-sm-2">
30+
<input type="text" id="symbol" name="symbol" class="form-control form-control-sm {{symbol.errors? 'is-invalid':''}}"
31+
required
32+
(change)="trade.symbol=trade.symbol.toUpperCase()"
33+
[appSymbolValidator]="symbols"
34+
[(ngModel)]="trade.symbol"
35+
[typeahead]="exchangeInfo"
36+
[typeaheadScrollable]="true"
37+
typeaheadOptionField="s"
38+
[typeaheadOptionsInScrollableView]="10"
39+
(typeaheadOnSelect)="onSymbolSelected($event)"
40+
autocomplete="off" #symbol="ngModel">
41+
</div>
42+
43+
<!--<div class="col-sm-2" *ngIf="symbol.invalid && (symbol.dirty || symbol.touched)"-->
44+
<!--class="alert alert-danger">-->
45+
46+
<!--<div *ngIf="symbol.errors.appSymbolValidator">-->
47+
<!--Symbol cannot be test.-->
48+
<!--</div>-->
49+
50+
<!--</div>-->
51+
<label for="asset" class="col-sm-1 col-form-label-sm">Asset</label>
52+
<div class="col-sm-2">
53+
<input type="text" id="asset" name="asset" class="form-control form-control-sm" [ngModel]="trade.asset" [disabled]="true">
54+
</div>
55+
<label for="asset" class="col-sm-1 col-form-label-sm">Cap</label>
56+
<div class="col-sm-2">
57+
<input type="text" id="cap" name="cap" class="form-control form-control-sm" [ngModel]="trade.cap" autocomplete="off">
58+
</div>
2959
</div>
3060
<!--<div class="form-group row">-->
3161
<!--<label for="status" class="col-sm-2 col-form-label">Status</label>-->
3262
<!--<div class="col-sm-10"><input type="text" id="status" class="form-control" [(value)]="trade.status" ></div>-->
3363
<!--</div>-->
3464
<div class="form-group row">
35-
<label for="buysellBtn" class="col-sm-2 col-form-label">Side</label>
65+
<label for="buysellBtn" class="col-sm-2 col-form-label-sm">Side</label>
3666
<div class="col-sm-10">
3767
<div class="btn-group btn-group-toggle" data-toggle="buttons" id="buysellBtn">
3868
<label class="btn btn-sm btn-outline-danger {{trade.isSell() ? 'active' : ''}}" *ngIf="mode.isCreate() || (!mode.isCreate() && trade.isSell())">
@@ -49,7 +79,8 @@ <h4 class="modal-title pull-left">Trade ({{mode.str()}} mode)</h4>
4979
<app-sl-details [trade]="trade" [mode]="mode"></app-sl-details>
5080
</div>
5181
<div class="modal-footer">
52-
<button type="button" class="btn btn-success" (click)="confirm()" *ngIf="!mode.isView()" >{{ mode.str() }}</button>
82+
<button type="button" class="btn btn-success" (click)="confirm()" *ngIf="!mode.isView()" [disabled]="tradeForm.invalid" >{{ mode.str() }}</button>
5383
<button type="button" class="btn btn-primary" (click)="decline()" >Cancel</button>
5484
</div>
85+
</form>
5586
</ng-template>

admin/src/app/trade-details/trade-details.component.ts

+20-5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ export class TradeDetailsComponent implements OnInit {
2323
mode: Mode;
2424
// trade: TradeDetails;
2525

26+
exchangeInfo: any[];
27+
symbols: Set<string>;
28+
2629
private modalRef: BsModalRef;
2730

2831
private config = {
@@ -60,7 +63,15 @@ export class TradeDetailsComponent implements OnInit {
6063

6164
if (this.mode.isCreate()) {
6265
this.trade = new TradeDetails(true);
66+
this.api.getExchangeInfo().subscribe(
67+
res => {
68+
this.exchangeInfo = res;
69+
this.symbols = new Set<string>();
70+
this.exchangeInfo.forEach(si => this.symbols.add(<string>si.s));
71+
}
72+
);
6373
} else {
74+
this.exchangeInfo = [];
6475
this.route.paramMap.pipe(
6576
switchMap(params => {
6677
return this.api.getActiveTradeInfo(params.get('id'));
@@ -71,12 +82,10 @@ export class TradeDetailsComponent implements OnInit {
7182
}
7283

7384
confirm() {
74-
this.api.addTrade(this.trade).subscribe(res => console.log(res),
75-
err => console.log(err),
76-
() => this.closeModal.bind(this)
85+
this.api.addTrade(this.trade).subscribe(
86+
res => this.closeModal(),
87+
err => console.log(err)
7788
);
78-
// console.log(this.trade);
79-
// this.closeModal();
8089
}
8190

8291
decline() {
@@ -87,4 +96,10 @@ export class TradeDetailsComponent implements OnInit {
8796
this.modalRef.hide();
8897
this.router.navigate(['/trades']);
8998
}
99+
100+
onSymbolSelected(event) {
101+
if (this.mode.isCreate()) {
102+
this.trade.asset = event.item.b;
103+
}
104+
}
90105
}

0 commit comments

Comments
 (0)