Skip to content

Commit 9529ae9

Browse files
committed
Added logs view
Absolute volume allocation for created trades
1 parent b47dfc5 commit 9529ae9

21 files changed

+280
-77
lines changed

API/APIServer.py

+9
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
from datetime import datetime, timedelta
2+
from distutils.log import Log
23

34
from flask import Flask, app, render_template
45
from flask_jwt_extended import JWTManager
56
from flask_restful import Api
67
from flask_cors import CORS
78

9+
from API.Endpoints.LogsEndpoint import LogsEndpoint
810
from API.Endpoints.APIexchangeInfoEndpoint import APIExchangeInfoEndpoint
911
from API.Endpoints.BalanceEndpoint import BalanceEndpoint
1012
from API.Endpoints.JWTEndpoint import JWTEndpoint
1113
from API.Endpoints.OrderBookEndpoint import OrderBookEndpoint
14+
from API.Endpoints.ProxyEndpoint import ProxyEndpoint
1215
from API.Endpoints.TradeEndpoint import TradeEndpoint
1316
from API.Endpoints.TradeListEndpoint import TradeListEndpoint
1417
from Bot.TradeHandler import TradeHandler
@@ -40,6 +43,12 @@ def __init__(self, trade_handler: TradeHandler):
4043
# self.api.add_resource(BalanceEndpoint, APIServer.API_PREFIX + '/balance',
4144
# resource_class_kwargs={'trade_handler': self.th})
4245

46+
self.api.add_resource(ProxyEndpoint, APIServer.API_PREFIX + '/proxy/icon',
47+
resource_class_kwargs={'trade_handler': self.th})
48+
49+
self.api.add_resource(LogsEndpoint, APIServer.API_PREFIX + '/logs/<file>',
50+
resource_class_kwargs={'trade_handler': self.th})
51+
4352
self.api.add_resource(OrderBookEndpoint, APIServer.API_PREFIX + '/orderbook/<symbol>',
4453
resource_class_kwargs={'trade_handler': self.th})
4554

API/Endpoints/LogsEndpoint.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from API.Endpoints.BotAPIResource import BotAPIResource
2+
from flask_jwt_extended import jwt_required
3+
4+
5+
class LogsEndpoint(BotAPIResource):
6+
7+
@jwt_required()
8+
def get(self, file):
9+
if not file:
10+
return self.list_files()
11+
12+
return self.get_file_contents(None if file == 'latest' else file)

API/Endpoints/ProxyEndpoint.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import io
2+
import requests
3+
4+
from flask import send_file
5+
6+
from API.Endpoints.BotAPIResource import BotAPIResource
7+
8+
9+
class ProxyEndpoint(BotAPIResource):
10+
11+
def get(self):
12+
r = requests.get('https://img.shields.io/github/stars/iilunin/crypto-bot.svg?style=social&label=Star&maxAge=2592000')
13+
return send_file(io.BytesIO(r.content), mimetype='image/svg+xml;charset=utf-8', as_attachment=False)

Bot/Strategy/TargetsAndStopLossStrategy.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def all_strategies(self):
153153

154154
def emergent_close_position(self):
155155
try:
156-
self.fx.cancel_open_orders(self.symbol())
156+
self.cancel_all_open_orders()
157157

158158
AccountBalances().update_balances(self.fx.get_all_balances_dict())
159159

Bot/Strategy/TradingStrategy.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,7 @@ def validate_target_orders(self, force_cancel_open_orders=False):
113113

114114
update_required = False
115115
if force_cancel_open_orders or (len(active_trade_targets) == 0 and has_open_orders):
116-
self.logInfo('Cancelling all Open orders')
117-
self.fx.cancel_open_orders(self.symbol())
116+
self.cancel_all_open_orders()
118117
else:
119118
for active_trade_target in active_trade_targets:
120119

@@ -145,6 +144,10 @@ def validate_target_orders(self, force_cancel_open_orders=False):
145144
if update_required:
146145
self.trigger_target_updated()
147146

147+
def cancel_all_open_orders(self):
148+
self.logInfo('Cancelling all Open orders for "{}"'.format(self.symbol()))
149+
self.fx.cancel_open_orders(self.symbol())
150+
148151
def _update_trade_target_status_change(self, t: Target, status: str) -> bool:
149152
if status == FXConnector.ORDER_STATUS_FILLED:
150153
t.set_completed()

Bot/TradeHandler.py

+1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ def remove_strategy(self, strategy: TradingStrategy, api_call=False):
9393
if api_call:
9494
strategy.set_trade_removed()
9595

96+
strategy.cancel_all_open_orders()
9697
self.fx.listen_symbols([s.symbol() for s in self.strategies], self.listen_handler, self.user_data_handler)
9798
self.socket_message_rcvd = False
9899

Docker/Dockerfile-botapi

+4-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ WORKDIR /usr/src/app
88
ENV \
99
TRADE_DIR=/usr/src/trades \
1010
TRADE_COMPLETED_DIR=/usr/src/complete_trades \
11-
CONF_DIR=/usr/src/configs
11+
CONF_DIR=/usr/src/configs \
12+
LOGS_DIR=/usr/src/logs
13+
14+
RUN mkdir -p /usr/src/logs
1215

1316
COPY requirements.txt /usr/src/app/
1417

Dockerfile

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ ENV \
4949
TRADE_DIR=/usr/src/trades \
5050
TRADE_COMPLETED_DIR=/usr/src/complete_trades \
5151
CONF_DIR=/usr/src/configs
52+
LOGS_DIR=/usr/src/logs
5253

5354
COPY requirements.txt /usr/src/app/
5455

Utils/Logger.py

+26
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
11
import logging
2+
import os
3+
from logging.handlers import TimedRotatingFileHandler, RotatingFileHandler
4+
from os import environ, path, listdir
5+
from os.path import isfile, join
26

37
LOG_FORMAT = '%(asctime)s[%(levelname)s][%(name)s|%(threadName)s]: %(message)s'
48
logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)
59

10+
LOG_DIR = environ.get('LOGS_DIR')
11+
CURRENT_LOG_FN = path.join(LOG_DIR, 'log.txt') if LOG_DIR else None
12+
13+
if LOG_DIR:
14+
handler = RotatingFileHandler(CURRENT_LOG_FN, maxBytes=1048576, backupCount=7)
15+
handler.setFormatter(logging.Formatter(LOG_FORMAT))
616

717
class Logger:
818
def __init__(self, logger=None):
919
self.logger = logger if logger else logging.getLogger(self._get_logger_name())
1020

21+
if LOG_DIR:
22+
self.logger.addHandler(handler)
23+
1124
def logInfo(self, msg):
1225
self.logger.log(logging.INFO, msg)
1326

@@ -20,6 +33,19 @@ def logError(self, msg):
2033
def logDebug(self, msg):
2134
self.logger.log(logging.DEBUG, msg)
2235

36+
def list_files(self):
37+
if not LOG_DIR:
38+
return None
39+
40+
return [f for f in listdir(LOG_DIR) if isfile(join(LOG_DIR, f))]
41+
42+
def get_file_contents(self, file):
43+
if not file:
44+
file = CURRENT_LOG_FN
45+
f = open(file, 'r')
46+
return f.read()
47+
48+
2349
def _get_logger_name(self):
2450
return self.__class__.__name__
2551

admin/src/app/app.component.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { Component } from '@angular/core';
2+
import { MatIconRegistry } from '@angular/material/icon';
23
import { MatSnackBar } from '@angular/material/snack-bar';
4+
import { DomSanitizer } from '@angular/platform-browser';
35
import { AuthService } from './auth.service';
6+
import { BotApi } from './botapi';
47
import { NotificationService, NotificatoinType } from './services/notification.service'
58

69
@Component({
@@ -15,7 +18,10 @@ export class AppComponent {
1518
constructor(
1619
private notificationService: NotificationService,
1720
private snackBar: MatSnackBar,
18-
public auth: AuthService
21+
public auth: AuthService,
22+
private matIconRegistry: MatIconRegistry,
23+
private domSanitizer: DomSanitizer,
24+
private bot: BotApi
1925
) {
2026
this.notificationService.notification$.subscribe(message => {
2127

@@ -25,6 +31,10 @@ export class AppComponent {
2531
panelClass: [this.getPanelClass(message.type)]});
2632
// panelClass: ["blue-snackbar"]});
2733
});
34+
35+
this.matIconRegistry.addSvgIcon(
36+
'my_git', domSanitizer.bypassSecurityTrustResourceUrl(`${this.bot.API_URL}/proxy/icon`)
37+
);
2838
}
2939

3040
private getPanelClass(type: NotificatoinType): string {

admin/src/app/app.module.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import {OverlayModule} from '@angular/cdk/overlay';
6565
import { MainTabsComponent } from './main-tabs/main-tabs.component';
6666
import { BalancesComponent } from './balances/balances.component';
6767
import { AutoCompleteComponent } from './auto-complete/auto-complete.component';
68+
import { LogsComponent } from './logs/logs.component';
6869
// endmaterial
6970
// const schemas: any[] = [];
7071
// schemas.push(CUSTOM_ELEMENTS_SCHEMA);
@@ -81,7 +82,8 @@ import { AutoCompleteComponent } from './auto-complete/auto-complete.component';
8182
LogInComponent,
8283
MainTabsComponent,
8384
BalancesComponent,
84-
AutoCompleteComponent
85+
AutoCompleteComponent,
86+
LogsComponent
8587
],
8688
imports: [
8789
CommonModule,

admin/src/app/botapi.ts

+7
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ export class BotApi {
3030
constructor(private http: HttpClient) {
3131
}
3232

33+
getRecentLogFileContents(filename='latest'): Observable<String> {
34+
return this.http.get<String>(`${this.API_URL}/logs/${filename}`).pipe(
35+
retry(this.RETRIES),
36+
catchError(this.handleError('getActiveTrades', '')));
37+
}
38+
3339
addTrade(trade: TradeDetails ): Observable<ApiResult> {
3440
const sanitizedTrade: any = Object.assign({}, trade, {stoploss: trade.stoploss});
3541
deleteProperty(sanitizedTrade, '_stoploss');
@@ -148,6 +154,7 @@ export class BotApi {
148154
console.log('BotApi: ' + message);
149155
}
150156

157+
151158
/**
152159
* Handle Http operation that failed.
153160
* Let the app continue.

admin/src/app/logs/logs.component.css

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/* make-scrollable {
2+
height: 800px;
3+
overflow-y: scroll;
4+
} */
5+
6+
.log-item {
7+
height: 32px;
8+
font-size: 16px;
9+
}
10+
11+
div {
12+
display: flex;
13+
flex-direction: row;
14+
align-items: center;
15+
box-sizing: border-box;
16+
padding: 0 16px;
17+
position: relative;
18+
height: inherit;
19+
}
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!-- <div class="full-width">
2+
<mat-list *ngFor="let log of logContents">
3+
<mat-list-item class="log-item"><p>{{log}}</p></mat-list-item>
4+
</mat-list>
5+
</div> -->
6+
<div class="full-width">
7+
<button mat-icon-button matTooltip="Refresh Log" (click)="refreshLogs()">
8+
<mat-icon>refresh</mat-icon>
9+
</button>
10+
<button mat-icon-button matTooltip="Copy Log" *ngIf="logContents">
11+
<mat-icon [cdkCopyToClipboard]="this.logContents.join('\n')">file_copy</mat-icon>
12+
</button>
13+
</div>
14+
<div class="full-width" *ngFor="let log of logContents">
15+
<p>{{log}}</p>
16+
</div>
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { LogsComponent } from './logs.component';
4+
5+
describe('LogsComponent', () => {
6+
let component: LogsComponent;
7+
let fixture: ComponentFixture<LogsComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
declarations: [ LogsComponent ]
12+
})
13+
.compileComponents();
14+
});
15+
16+
beforeEach(() => {
17+
fixture = TestBed.createComponent(LogsComponent);
18+
component = fixture.componentInstance;
19+
fixture.detectChanges();
20+
});
21+
22+
it('should create', () => {
23+
expect(component).toBeTruthy();
24+
});
25+
});

admin/src/app/logs/logs.component.ts

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Component, OnInit } from '@angular/core';
2+
import { BotApi } from '../botapi';
3+
4+
@Component({
5+
selector: 'app-logs',
6+
templateUrl: './logs.component.html',
7+
styleUrls: ['./logs.component.css']
8+
})
9+
export class LogsComponent implements OnInit {
10+
11+
logContents: String[];
12+
13+
constructor(private api: BotApi) { }
14+
15+
ngOnInit(): void {
16+
this.refreshLogs();
17+
}
18+
19+
refreshLogs(): void {
20+
this.api.getRecentLogFileContents().subscribe(r => {
21+
if (r){
22+
this.logContents = r.trim().split('\n').reverse();
23+
}
24+
})
25+
}
26+
27+
}

admin/src/app/main-tabs/main-tabs.component.css

+16-1
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,19 @@
2424

2525
h1.app-name {
2626
margin-left: 16px;
27-
}
27+
}
28+
29+
mat-icon[name=git] {
30+
width: 100px;
31+
padding: 10 px;
32+
}
33+
34+
span[name=gitcontainer]{
35+
align-content: center;
36+
text-align: center;
37+
justify-content: center;
38+
}
39+
40+
.spacer {
41+
flex: 1 1 auto;
42+
}

admin/src/app/main-tabs/main-tabs.component.html

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
<div class="toolbar-container" *ngIf="auth.isLoggedIn()">
22
<mat-toolbar color="primary" class="menu-toolbar">
33
<button mat-icon-button (click)="snav.toggle()"><mat-icon>menu</mat-icon></button>
4-
<h1 class="app-name">Crypto Bot</h1>
4+
<span>Crypto Trading Bot</span>
5+
<span class="spacer"></span>
6+
<!-- <a href="https://github.com/iilunin/crypto-bot/" target="_blank" rel="noopener"> -->
7+
<mat-icon aria-label="Example thumbs up SVG icon" svgIcon="my_git" name="git" matTooltip="give the repo a star"></mat-icon>
8+
<!-- </a> -->
59
</mat-toolbar>
610

711
<mat-sidenav-container class="side-nav-container" [style.marginTop.px]="56">
@@ -19,6 +23,12 @@ <h1 class="app-name">Crypto Bot</h1>
1923
<div mat-line>Balances</div>
2024
</mat-list-item>
2125
</a>
26+
<a routerLink="/logs">
27+
<mat-list-item>
28+
<mat-icon mat-list-icon>file_copy</mat-icon>
29+
<div mat-line>Logs</div>
30+
</mat-list-item>
31+
</a>
2232
<mat-divider></mat-divider>
2333
<a routerLink="/" (click)="auth.logout()">
2434
<mat-list-item>

0 commit comments

Comments
 (0)