... | ... |
@@ -0,0 +1,33 @@ |
1 |
+#!/usr/bin/env python3 |
|
2 |
+ |
|
3 |
+import time |
|
4 |
+import datetime |
|
5 |
+ |
|
6 |
+import bot |
|
7 |
+import queries |
|
8 |
+ |
|
9 |
+from binance.exceptions import BinanceAPIException |
|
10 |
+ |
|
11 |
+def cancel_current_best_offer(best_offer_price_, best_bid_price_): |
|
12 |
+ print(f'\n| ###{datetime.datetime.now().strftime("%H:%M")}###') |
|
13 |
+ print(f'| BID at price {best_bid_price_} was EXECUTED;') |
|
14 |
+ |
|
15 |
+ try: |
|
16 |
+ bot.CLIENT.cancel_order(symbol=bot.TRADING_PAIR, orderId=queries.get_order_id(best_offer_price_)) # CANCEL currentlly 'BEST OFFER' |
|
17 |
+ print(f'| Cancelling BEST OFFER: {best_offer_price_}') |
|
18 |
+ except BinanceAPIException: |
|
19 |
+ print('[!!!] Error while canceling BEST OFFER') |
|
20 |
+ |
|
21 |
+# A function for replacing the executed BID |
|
22 |
+def update_rel_to_best_bid(best_offer_price_, best_bid_price_): |
|
23 |
+ if not queries.order_active(best_offer_price_): # If NEW best OFFER order is not already or yet present on the market |
|
24 |
+ print(f'| Placing NEW BEST OFFER at lower price: {best_offer_price_}') |
|
25 |
+ bot.CLIENT.order_limit_sell(symbol=bot.TRADING_PAIR, quantity=bot.COINS_PER_ORDER, price=best_offer_price_) # Placing an OFFER (NEW best OFFER) at a WINDOW price |
|
26 |
+ time.sleep(1) |
|
27 |
+ |
|
28 |
+ if not queries.order_active(best_bid_price_): # If NEW best BID order is not already or yet present on the market |
|
29 |
+ print(f'| Placing NEW BEST BID at lower price: {best_bid_price_}') |
|
30 |
+ bot.CLIENT.order_limit_buy(symbol=bot.TRADING_PAIR, quantity=bot.COINS_PER_ORDER, price=best_bid_price_) # Replacing executed BID with NEW best BID |
|
31 |
+ time.sleep(1) |
|
32 |
+ |
|
33 |
+ print('| ########### \n') |
... | ... |
@@ -0,0 +1,165 @@ |
1 |
+#!/usr/bin/env python3 |
|
2 |
+ |
|
3 |
+# pip3 install -r requirements.txt |
|
4 |
+ |
|
5 |
+import time |
|
6 |
+import datetime |
|
7 |
+import json |
|
8 |
+import sys |
|
9 |
+ |
|
10 |
+import bids |
|
11 |
+import offers |
|
12 |
+import reports |
|
13 |
+import calc |
|
14 |
+import launch |
|
15 |
+import queries |
|
16 |
+ |
|
17 |
+from binance.client import Client |
|
18 |
+ |
|
19 |
+#========== Reading CONFIG file ==========# |
|
20 |
+with open('config.json', 'r') as config_file: # Open and read the 'config.json' file |
|
21 |
+ config_data = json.load(config_file) |
|
22 |
+ |
|
23 |
+ #---------- Binance_bot data ----------# |
|
24 |
+ API_KEY = config_data['Binance_bot']['api_key'] |
|
25 |
+ API_SECRET = config_data['Binance_bot']['api_secret'] |
|
26 |
+ |
|
27 |
+ TRADING_ASSET = config_data['Binance_bot']['asset'] # Coin to trade against of (ex. BTC) |
|
28 |
+ TRADING_COIN = config_data['Binance_bot']['coin'] # Trading coin (ex. BNB) |
|
29 |
+ TRADING_PAIR = str(TRADING_COIN + TRADING_ASSET) # Trading pair: BNB for BTC => BNBBTC |
|
30 |
+ |
|
31 |
+ SPREAD = int(config_data['Binance_bot']['spread']) # Distance between: BID => OFFER in number of STEPS (see line below) |
|
32 |
+ SPREAD_TO_PRINT = config_data['Binance_bot']['spread'] |
|
33 |
+ |
|
34 |
+ STEP = float(config_data['Binance_bot']['step']) # Distance between: order(of any type) => order(of any type) in asset value |
|
35 |
+ STEP_TO_PRINT = config_data['Binance_bot']['step'] |
|
36 |
+ |
|
37 |
+ COINS_PER_ORDER = float(config_data['Binance_bot']['coins_per_order']) # Quantity of coins for one BID |
|
38 |
+ COINS_PER_ORDER_TO_PRINT = config_data['Binance_bot']['coins_per_order'] |
|
39 |
+ |
|
40 |
+ #---------- Email_sender data ----------# |
|
41 |
+ SENDER_ADDRESS = config_data['Email_sender']['sender_address'] |
|
42 |
+ SENDER_PASS = config_data['Email_sender']['sender_pass'] |
|
43 |
+ |
|
44 |
+ RECEIVER_ADDRESS = config_data['Email_sender']['receiver_address'] |
|
45 |
+ |
|
46 |
+#========== Connecting to API ==========# |
|
47 |
+CLIENT = Client(API_KEY, API_SECRET) # Connecting to the Binance API |
|
48 |
+ |
|
49 |
+#========== Setting Up Constants ==========# |
|
50 |
+INIT_COIN_PRICE = queries.get_coin_price() # An initial coin price |
|
51 |
+NUM_DECIMAL_PLACES = calc.count_decimal_places() # Round all prices to the constant number of decimal places |
|
52 |
+ |
|
53 |
+PRICES_ARR = calc.generate_grid_of_prices() # An array of initial prices |
|
54 |
+ |
|
55 |
+#========== If ran as main ==========# |
|
56 |
+if __name__ == "__main__": |
|
57 |
+ |
|
58 |
+ #========== Setting UP ==========# |
|
59 |
+ current_day = datetime.datetime.now().strftime("%d") # Current day of month |
|
60 |
+ current_week = datetime.datetime.now().strftime("%W") # Current day of the week |
|
61 |
+ current_month = datetime.datetime.now().strftime("%b") # Current month |
|
62 |
+ |
|
63 |
+ tmp_day = current_day # Temporal variable for holding current day of month |
|
64 |
+ tmp_week = current_week # Temporal variable for holding current day of the week |
|
65 |
+ tmp_month = current_month # Temporal variable for holding current month |
|
66 |
+ |
|
67 |
+ asset_balance = CLIENT.get_asset_balance(asset=TRADING_ASSET) # An array of info about the original balance |
|
68 |
+ asset_balance_free = float(asset_balance['free']) # Original balance of free asset |
|
69 |
+ coin_balace = CLIENT.get_asset_balance(asset=TRADING_COIN) # An array of info about the original coin balance |
|
70 |
+ coin_balance_free = float(coin_balace['free']) # Original balance of free coin |
|
71 |
+ |
|
72 |
+ window_price = calc.find_current_window_price() # An empty window for BID/OFFER orders |
|
73 |
+ best_offer_price = calc.find_best_offer_price(window_price) # Current best OFFER |
|
74 |
+ best_bid_price = calc.find_best_bid_price(best_offer_price) # Current best BID |
|
75 |
+ |
|
76 |
+ #========== Printing INFO ==========# |
|
77 |
+ launch.start_trading_ui(asset_balance_free, coin_balance_free) |
|
78 |
+ |
|
79 |
+ print('\n| ----------') |
|
80 |
+ print(f'| Best OFFER: {best_offer_price}') |
|
81 |
+ print(f'| WINDOW: {window_price}') |
|
82 |
+ print(f'| Best BID: {best_bid_price}') |
|
83 |
+ print('| ----------\n') |
|
84 |
+ |
|
85 |
+ #========== Placing Initial Orders ==========# |
|
86 |
+ launch.place_init_orders(best_offer_price, window_price, best_bid_price) |
|
87 |
+ |
|
88 |
+ #========== Trading Loop ==========# |
|
89 |
+ while True: # The MAIN trading loop |
|
90 |
+ |
|
91 |
+ try: |
|
92 |
+ time.sleep(3) # Each 10 seconds send requests to the market |
|
93 |
+ |
|
94 |
+ current_day = datetime.datetime.now().strftime("%d") # Update the current day of month |
|
95 |
+ current_week = datetime.datetime.now().strftime("%W") # Update the current day of the week |
|
96 |
+ current_month = datetime.datetime.now().strftime("%b") # Update the current month |
|
97 |
+ |
|
98 |
+ if (not queries.order_active(best_offer_price) and queries.get_order_status(best_offer_price) == 'FILLED' |
|
99 |
+ and queries.enough_balance(asset_balance_free, coin_balance_free)): # If the current best-deal OFFER was executed and there is enough balance to continue |
|
100 |
+ |
|
101 |
+ offers.cancel_current_best_bid(best_offer_price, best_bid_price) # Cancel current best BID |
|
102 |
+ |
|
103 |
+ window_price = calc.up_current_window_price(window_price) # New empty window for BID/OFFER orders |
|
104 |
+ best_offer_price = calc.find_best_offer_price(window_price) # New best OFFER |
|
105 |
+ best_bid_price = calc.find_best_bid_price(best_offer_price) # Current best BID |
|
106 |
+ |
|
107 |
+ offers.update_rel_to_best_offer(best_offer_price, best_bid_price) |
|
108 |
+ reports.save_order("SELL", best_offer_price) |
|
109 |
+ |
|
110 |
+ print('\n| ----------') |
|
111 |
+ print(f'| Best OFFER: {best_offer_price}') |
|
112 |
+ print(f'| WINDOW: {window_price}') |
|
113 |
+ print(f'| Best BID: {best_bid_price}') |
|
114 |
+ print('| ----------\n') |
|
115 |
+ |
|
116 |
+ if (not queries.order_active(best_bid_price) and queries.get_order_status(best_bid_price) == 'FILLED' |
|
117 |
+ and queries.enough_balance(asset_balance_free, coin_balance_free)): # If the current best-deal BID was executed and there is enough balance to continue |
|
118 |
+ |
|
119 |
+ bids.cancel_current_best_offer(best_offer_price, best_bid_price) # Cancel current best OFFER |
|
120 |
+ |
|
121 |
+ window_price = calc.down_current_window_price(window_price) # New empty window for BID/OFFER orders |
|
122 |
+ best_offer_price = calc.find_best_offer_price(window_price) # New best OFFER |
|
123 |
+ best_bid_price = calc.find_best_bid_price(best_offer_price) # Current best BID |
|
124 |
+ |
|
125 |
+ bids.update_rel_to_best_bid(best_offer_price, best_bid_price) |
|
126 |
+ reports.save_order("BUY", best_bid_price) |
|
127 |
+ |
|
128 |
+ print('\n| ----------') |
|
129 |
+ print(f'| Best OFFER: {best_offer_price}') |
|
130 |
+ print(f'| WINDOW: {window_price}') |
|
131 |
+ print(f'| Best BID: {best_bid_price}') |
|
132 |
+ print('| ----------\n') |
|
133 |
+ |
|
134 |
+ if current_day != tmp_day: # If day has passed |
|
135 |
+ |
|
136 |
+ #reports.daily_report(queries.num_closed_inday_offers(), calc.calc_asset_profit(queries.num_closed_inday_offers())) |
|
137 |
+ tmp_day = current_day |
|
138 |
+ |
|
139 |
+ #print('\n| Daily Report Generated\n') |
|
140 |
+ |
|
141 |
+ if current_week != tmp_week: # If week has passed |
|
142 |
+ |
|
143 |
+ reports.weekly_report(queries.num_closed_inweek_offers(), calc.calc_asset_profit(queries.num_closed_inweek_offers())) |
|
144 |
+ tmp_week = current_week |
|
145 |
+ |
|
146 |
+ print('\n| Weekly Report Generated\n') |
|
147 |
+ |
|
148 |
+ if current_month != tmp_month: # If month has passed |
|
149 |
+ |
|
150 |
+ #reports.monthly_report(queries.num_closed_inmonth_offers(), calc.calc_asset_profit(queries.num_closed_inmonth_offers())) |
|
151 |
+ tmp_month = current_month |
|
152 |
+ |
|
153 |
+ #print('\n| Monthly Report Generated\n') |
|
154 |
+ |
|
155 |
+ else: |
|
156 |
+ continue |
|
157 |
+ |
|
158 |
+ except KeyboardInterrupt: |
|
159 |
+ sys.exit() |
|
160 |
+ |
|
161 |
+ except Exception as e: |
|
162 |
+ print(f'[!!!] [{datetime.datetime.now().strftime("%H:%M")}] {e}') |
|
163 |
+ with open('errors.log', 'a') as errors_log: |
|
164 |
+ errors_log.write(f'[{datetime.datetime.now().strftime("%d %b %Y; %H:%M")}] {e}\n') |
|
165 |
+ continue |
... | ... |
@@ -0,0 +1,56 @@ |
1 |
+#!/usr/bin/env python3 |
|
2 |
+ |
|
3 |
+import bot |
|
4 |
+ |
|
5 |
+# A function to generate the grid of prices for future orders |
|
6 |
+def generate_grid_of_prices(): |
|
7 |
+ up_to_price = bot.INIT_COIN_PRICE * 10; # Generate array up to this price |
|
8 |
+ grid_arr = [] |
|
9 |
+ order_price = 0 # A starting price |
|
10 |
+ |
|
11 |
+ while order_price < up_to_price: # While the generated price is still smaller than the maximum possible price |
|
12 |
+ grid_arr.append(round(order_price, bot.NUM_DECIMAL_PLACES)) # Append a new price to the array of prices |
|
13 |
+ order_price = round(order_price + bot.STEP, bot.NUM_DECIMAL_PLACES) # Add the STEP value to the future price |
|
14 |
+ |
|
15 |
+ return grid_arr |
|
16 |
+ |
|
17 |
+# A function for finding the current WINDOW price |
|
18 |
+def find_current_window_price(): |
|
19 |
+ for price in bot.PRICES_ARR: # Iterate through the array of prices |
|
20 |
+ if price >= bot.INIT_COIN_PRICE: # If the the price is larger than the current price |
|
21 |
+ return price |
|
22 |
+ break; |
|
23 |
+ else: |
|
24 |
+ continue; |
|
25 |
+ |
|
26 |
+ return None; |
|
27 |
+ |
|
28 |
+def find_best_offer_price(window_price_): |
|
29 |
+ window_price_i = bot.PRICES_ARR.index(window_price_) # Index value of a window price |
|
30 |
+ |
|
31 |
+ if bot.SPREAD % 2 == 0: # If SPREAD is even |
|
32 |
+ return bot.PRICES_ARR[window_price_i + int(bot.SPREAD / 2)] # Return the value whose index is half SPREAD higher than the WINDOW price |
|
33 |
+ else: # If SPREAD is odd |
|
34 |
+ return bot.PRICES_ARR[window_price_i + (int(bot.SPREAD / 2) + 1)] # Return the value whose index is one SPREAD higher than the WINDOW price |
|
35 |
+ |
|
36 |
+def find_best_bid_price(offer_price): |
|
37 |
+ offer_price_i = bot.PRICES_ARR.index(offer_price) # Index value of a window price |
|
38 |
+ |
|
39 |
+ return bot.PRICES_ARR[offer_price_i - (bot.SPREAD + 1)] # Return the value whose index is SPREAD + 1 lower than the OFFER price |
|
40 |
+ |
|
41 |
+def up_current_window_price(window_price_): |
|
42 |
+ return bot.PRICES_ARR[bot.PRICES_ARR.index(window_price_) + 1] # + 1 STEP (adding 1 to index, but the next value is 1 STEP larger) |
|
43 |
+ |
|
44 |
+def down_current_window_price(window_price_): |
|
45 |
+ return bot.PRICES_ARR[bot.PRICES_ARR.index(window_price_) - 1] # - 1 STEP (subtracting 1 from index, but the previous value is 1 STEP lower) |
|
46 |
+ |
|
47 |
+def count_decimal_places(): |
|
48 |
+ init_coin_price_str = str(bot.INIT_COIN_PRICE) |
|
49 |
+ |
|
50 |
+ return int(init_coin_price_str[::-1].find('.')) # Count all simbols until meeting a '.' |
|
51 |
+ |
|
52 |
+# A function for calculating the profit |
|
53 |
+def calc_asset_profit(num_closed_spreads): |
|
54 |
+ asset_profit = float(round(num_closed_spreads * (bot.SPREAD * bot.STEP * bot.COINS_PER_ORDER), bot.NUM_DECIMAL_PLACES)) |
|
55 |
+ |
|
56 |
+ return asset_profit |
... | ... |
@@ -0,0 +1,16 @@ |
1 |
+{ |
|
2 |
+ "Binance_bot": { |
|
3 |
+ "api_key":"", |
|
4 |
+ "api_secret":"", |
|
5 |
+ "asset":"", |
|
6 |
+ "coin":"", |
|
7 |
+ "spread":"", |
|
8 |
+ "step":"", |
|
9 |
+ "coins_per_order":"" |
|
10 |
+ }, |
|
11 |
+ "Email_sender": { |
|
12 |
+ "sender_address":"", |
|
13 |
+ "sender_pass":"", |
|
14 |
+ "receiver_address":"" |
|
15 |
+ } |
|
16 |
+} |
... | ... |
@@ -0,0 +1,59 @@ |
1 |
+#!/usr/bin/env python3 |
|
2 |
+ |
|
3 |
+import datetime |
|
4 |
+import sys |
|
5 |
+ |
|
6 |
+import bot |
|
7 |
+import queries |
|
8 |
+ |
|
9 |
+# A function for turning the float into a printable and understandable form |
|
10 |
+# (e.g 2.22e-05 to '0.0000222') |
|
11 |
+def _float_to_printable(num): |
|
12 |
+ num_str = str(num) |
|
13 |
+ |
|
14 |
+ # If the given float is of the form x.xxx...xxxe-xxx |
|
15 |
+ if 'e' in num_str: |
|
16 |
+ num_of_zeros = int(num_str[len(num_str) - 1]) - 1 # Number of zeros after comma |
|
17 |
+ e_index = num_str.index('e') |
|
18 |
+ decimals = num_str[0] + num_str[2:e_index] # Numbers after zeros (e.g all xxx in 0.000...0xxx) |
|
19 |
+ printable_float = '0.' + '0' * num_of_zeros + decimals # Combine the string float |
|
20 |
+ |
|
21 |
+ return printable_float |
|
22 |
+ else: |
|
23 |
+ return num_str |
|
24 |
+ |
|
25 |
+# A function for displaying the 'start_trading' initial interface |
|
26 |
+def start_trading_ui(asset_balance_free_, coin_balance_free_): |
|
27 |
+ |
|
28 |
+ print('\n**************************') |
|
29 |
+ print('Starting trading session:') |
|
30 |
+ print('--------------------------') |
|
31 |
+ print(f'Time: {datetime.datetime.now().strftime("%H:%M")}') |
|
32 |
+ print('--------------------------') |
|
33 |
+ print(f'Step: {bot.STEP_TO_PRINT} {bot.TRADING_ASSET.lower()}') |
|
34 |
+ print(f'Spread: {bot.SPREAD_TO_PRINT} ({_float_to_printable(round(bot.SPREAD * bot.STEP, bot.NUM_DECIMAL_PLACES))}) {bot.TRADING_ASSET.lower()}') |
|
35 |
+ print(f'{bot.TRADING_COIN} per BID: {bot.COINS_PER_ORDER_TO_PRINT}') |
|
36 |
+ print('**************************\n') |
|
37 |
+ |
|
38 |
+ while True: |
|
39 |
+ start = input('Start trading(y/n): ') |
|
40 |
+ if start.lower() == 'y': |
|
41 |
+ break |
|
42 |
+ elif start.lower() == 'n': |
|
43 |
+ sys.exit() |
|
44 |
+ else: |
|
45 |
+ continue |
|
46 |
+ |
|
47 |
+ if not queries.enough_balance(asset_balance_free_, coin_balance_free_): # If there is not enough balance to start trading |
|
48 |
+ print('\n| Not enough balance in asset or coin!') |
|
49 |
+ sys.exit() |
|
50 |
+ |
|
51 |
+# A fuction for placing the best BID and OFFER orders on the market |
|
52 |
+def place_init_orders(best_offer_price_, window_price_, best_bid_price_): |
|
53 |
+ if not queries.order_active(best_offer_price_): # If the 'BEST OFFER' order is not already or yet present on the market |
|
54 |
+ print(f'| Placing BEST OFFER at: {best_offer_price_}') |
|
55 |
+ bot.CLIENT.order_limit_sell(symbol=bot.TRADING_PAIR, quantity=bot.COINS_PER_ORDER, price=best_offer_price_) |
|
56 |
+ |
|
57 |
+ if not queries.order_active(best_bid_price_): # If the 'BEST BID' order is not already or yet present on the market |
|
58 |
+ print(f'| Placing BEST BID at: {best_bid_price_}') |
|
59 |
+ bot.CLIENT.order_limit_buy(symbol=bot.TRADING_PAIR, quantity=bot.COINS_PER_ORDER, price=best_bid_price_) |
... | ... |
@@ -0,0 +1,33 @@ |
1 |
+#!/usr/bin/env python3 |
|
2 |
+ |
|
3 |
+import time |
|
4 |
+import datetime |
|
5 |
+ |
|
6 |
+import bot |
|
7 |
+import queries |
|
8 |
+ |
|
9 |
+from binance.exceptions import BinanceAPIException |
|
10 |
+ |
|
11 |
+def cancel_current_best_bid(best_offer_price_, best_bid_price_): |
|
12 |
+ print(f'\n| @@@{datetime.datetime.now().strftime("%H:%M")}@@@') |
|
13 |
+ print(f'| OFFER at price {best_offer_price_} was EXECUTED;') |
|
14 |
+ |
|
15 |
+ try: |
|
16 |
+ bot.CLIENT.cancel_order(symbol=bot.TRADING_PAIR, orderId=queries.get_order_id(best_bid_price_)) # CANCEL currentlly 'BEST BID' |
|
17 |
+ print(f'| Cancelling BEST BID: {best_bid_price_}') |
|
18 |
+ except BinanceAPIException: |
|
19 |
+ print('[!!!] Error while canceling BEST BID') |
|
20 |
+ |
|
21 |
+# A function for replacing the executed OFFER |
|
22 |
+def update_rel_to_best_offer(best_offer_price_, best_bid_price_): |
|
23 |
+ if not queries.order_active(best_offer_price_): # If the NEW best OFFER order is not already or yet present on the market |
|
24 |
+ print(f'| Placing NEW BEST OFFER at higher price: {best_offer_price_}') |
|
25 |
+ bot.CLIENT.order_limit_sell(symbol=bot.TRADING_PAIR, quantity=bot.COINS_PER_ORDER, price=best_offer_price_) # Replacing executed OFFER with NEW best OFFER |
|
26 |
+ time.sleep(1) |
|
27 |
+ |
|
28 |
+ if not queries.order_active(best_bid_price_): # If the NEW best BID order is not already or yet present on the market |
|
29 |
+ print(f'| Placing NEW BEST BID at higher price: {best_bid_price_}') |
|
30 |
+ bot.CLIENT.order_limit_buy(symbol=bot.TRADING_PAIR, quantity=bot.COINS_PER_ORDER, price=best_bid_price_) # Placing a BID (NEW best BID) at a WINDOW price |
|
31 |
+ time.sleep(1) |
|
32 |
+ |
|
33 |
+ print('| @@@@@@@@@@@\n') |
... | ... |
@@ -0,0 +1,119 @@ |
1 |
+#!/usr/bin/env python3 |
|
2 |
+ |
|
3 |
+import datetime |
|
4 |
+ |
|
5 |
+import bot |
|
6 |
+ |
|
7 |
+# A function to get the price of a certain coin |
|
8 |
+def get_coin_price(): |
|
9 |
+ current_coin_price = float(bot.CLIENT.get_ticker(symbol=bot.TRADING_PAIR)['askPrice']) |
|
10 |
+ |
|
11 |
+ return current_coin_price |
|
12 |
+ |
|
13 |
+# A function to get the id of the order at a certain price |
|
14 |
+def get_order_id(coin_price): |
|
15 |
+ orders_arr = bot.CLIENT.get_open_orders(symbol=bot.TRADING_PAIR) # An array of active orders for the currentlly trading coin |
|
16 |
+ |
|
17 |
+ for order in orders_arr: |
|
18 |
+ if round(float(order['price']), bot.NUM_DECIMAL_PLACES) == coin_price: |
|
19 |
+ return order['orderId'] # Returns the order id |
|
20 |
+ else: |
|
21 |
+ continue |
|
22 |
+ |
|
23 |
+# A function to get the status of the order at a certain price |
|
24 |
+def get_order_status(coin_price): |
|
25 |
+ orders_arr = bot.CLIENT.get_all_orders(symbol=bot.TRADING_PAIR, limit=2) |
|
26 |
+ |
|
27 |
+ for order in orders_arr: |
|
28 |
+ if round(float(order['price']), bot.NUM_DECIMAL_PLACES) == coin_price: |
|
29 |
+ return order['status'] |
|
30 |
+ else: |
|
31 |
+ continue |
|
32 |
+ else: |
|
33 |
+ return None |
|
34 |
+ |
|
35 |
+# A function to check whether an order at a certain price is present on the market |
|
36 |
+def order_active(coin_price): |
|
37 |
+ orders_arr = bot.CLIENT.get_open_orders(symbol=bot.TRADING_PAIR) # An array of active orders for the currentlly trading coin |
|
38 |
+ |
|
39 |
+ for order in orders_arr: |
|
40 |
+ if round(float(order['price']), bot.NUM_DECIMAL_PLACES) == coin_price: |
|
41 |
+ return True |
|
42 |
+ else: |
|
43 |
+ return False |
|
44 |
+ |
|
45 |
+# A function to check if there is enough balance in both, asset and coin for one more order |
|
46 |
+def enough_balance(asset_balance_free_, coin_balance_free_): |
|
47 |
+ if (asset_balance_free_ >= bot.COINS_PER_ORDER * get_coin_price() and |
|
48 |
+ coin_balance_free_ >= bot.COINS_PER_ORDER): |
|
49 |
+ |
|
50 |
+ return True |
|
51 |
+ else: |
|
52 |
+ return False |
|
53 |
+ |
|
54 |
+# A function to convert the server-time to the datetime object |
|
55 |
+def timestamp_to_date(timestamp): |
|
56 |
+ global CLIENT |
|
57 |
+ |
|
58 |
+ return datetime.datetime.fromtimestamp(float(timestamp)/1000) |
|
59 |
+ |
|
60 |
+# A function to count the IDs of orders, closed within last 24h |
|
61 |
+def num_closed_inday_offers(): |
|
62 |
+ server_time = timestamp_to_date(bot.CLIENT.get_server_time()['serverTime']) # 'To' date |
|
63 |
+ from_date = server_time - datetime.timedelta(days=1) # 'From' date |
|
64 |
+ |
|
65 |
+ orders_arr = bot.CLIENT.get_all_orders(symbol=bot.TRADING_PAIR, limit=500) |
|
66 |
+ decimal_places = len(str(bot.COINS_PER_ORDER).split('.')[1]) |
|
67 |
+ |
|
68 |
+ count = 0 |
|
69 |
+ for order in orders_arr: |
|
70 |
+ if (order['status'] == 'FILLED' and order['side'] == 'SELL' and round(float(order['origQty']), decimal_places) == bot.COINS_PER_ORDER |
|
71 |
+ and (from_date < timestamp_to_date(order['updateTime']) < server_time)): |
|
72 |
+ |
|
73 |
+ count += 1 |
|
74 |
+ |
|
75 |
+ return count |
|
76 |
+ |
|
77 |
+ |
|
78 |
+# A function to count the IDs of orders, closed within last week |
|
79 |
+def num_closed_inweek_offers(): |
|
80 |
+ server_time = timestamp_to_date(bot.CLIENT.get_server_time()['serverTime']) # 'To' date |
|
81 |
+ from_date = server_time - datetime.timedelta(days=7) # 'From' date |
|
82 |
+ |
|
83 |
+ orders_arr = bot.CLIENT.get_all_orders(symbol=bot.TRADING_PAIR, limit=500) |
|
84 |
+ decimal_places = len(str(bot.COINS_PER_ORDER).split('.')[1]) |
|
85 |
+ |
|
86 |
+ count = 0 |
|
87 |
+ for order in orders_arr: |
|
88 |
+ if (order['status'] == 'FILLED' and order['side'] == 'SELL' and round(float(order['origQty']), decimal_places) == bot.COINS_PER_ORDER |
|
89 |
+ and (from_date < timestamp_to_date(order['updateTime']) < server_time)): |
|
90 |
+ |
|
91 |
+ count += 1 |
|
92 |
+ |
|
93 |
+ return count |
|
94 |
+ |
|
95 |
+# A function for getting the number of the previous month (ex. dec=12 => return nov=11) |
|
96 |
+def _get_past_month_num(): |
|
97 |
+ current_month_num = int(datetime.datetime.now().strftime("%m")) # Get the current number of a month (1 - 12) |
|
98 |
+ |
|
99 |
+ if current_month_num == 1: |
|
100 |
+ return 12 |
|
101 |
+ else: |
|
102 |
+ return current_month_num - 1 |
|
103 |
+ |
|
104 |
+# A function to count the IDs of orders, closed within last month |
|
105 |
+def num_closed_inmonth_offers(): |
|
106 |
+ server_time = timestamp_to_date(bot.CLIENT.get_server_time()['serverTime']) # 'To' date |
|
107 |
+ month_before_date = server_time.replace(month=_get_past_month_num()) # Date a month before |
|
108 |
+ |
|
109 |
+ orders_arr = bot.CLIENT.get_all_orders(symbol=bot.TRADING_PAIR, limit=500) |
|
110 |
+ decimal_places = len(str(bot.COINS_PER_ORDER).split('.')[1]) |
|
111 |
+ |
|
112 |
+ count = 0 |
|
113 |
+ for order in orders_arr: |
|
114 |
+ if (order['status'] == 'FILLED' and order['side'] == 'SELL' and round(float(order['origQty']), decimal_places) == bot.COINS_PER_ORDER |
|
115 |
+ and (month_before_date < timestamp_to_date(order['updateTime']) < server_time)): |
|
116 |
+ |
|
117 |
+ count += 1 |
|
118 |
+ |
|
119 |
+ return count |
... | ... |
@@ -0,0 +1,129 @@ |
1 |
+#!/usr/bin/env python3 |
|
2 |
+ |
|
3 |
+import datetime |
|
4 |
+import csv |
|
5 |
+ |
|
6 |
+import bot |
|
7 |
+import send |
|
8 |
+ |
|
9 |
+# A function for constructing the daily report in a '.csv' file |
|
10 |
+def daily_report(num_closed_spreads, asset_profit): |
|
11 |
+ current_date = datetime.datetime.now() # Current date |
|
12 |
+ day_before_date = current_date - datetime.timedelta(days=1) # Date a day before |
|
13 |
+ |
|
14 |
+ day_before_date_str = day_before_date.strftime("%d.%m.%Y") |
|
15 |
+ |
|
16 |
+ report_name = day_before_date_str + '_daily.csv' |
|
17 |
+ report_path = 'reports/daily/' + report_name |
|
18 |
+ |
|
19 |
+ with open(report_path, 'w') as database: |
|
20 |
+ |
|
21 |
+ headers =['date', 'closed', 'profit'] |
|
22 |
+ csv_writer = csv.DictWriter(database, headers) |
|
23 |
+ |
|
24 |
+ csv_writer.writeheader() |
|
25 |
+ |
|
26 |
+ csv_writer.writerow({'date':day_before_date_str, 'closed':str(num_closed_spreads), 'profit':str(asset_profit)}) |
|
27 |
+ |
|
28 |
+ try: # Try to send the generated report via email |
|
29 |
+ send.send_daily_report(report_name, day_before_date_str) |
|
30 |
+ print('\n| Daily Report has been Sent\n') |
|
31 |
+ |
|
32 |
+ except FileNotFoundError: |
|
33 |
+ print('[!!!] Cannot send the email: Report does not exists') |
|
34 |
+ |
|
35 |
+ except: |
|
36 |
+ print('[!!!] Cannot send the email: Unexpected Error') |
|
37 |
+ |
|
38 |
+# A function for constructing the weekly report in a '.csv' file |
|
39 |
+def weekly_report(num_closed_spreads, asset_profit): |
|
40 |
+ current_date = datetime.datetime.now() # Current date |
|
41 |
+ week_before_date = current_date - datetime.timedelta(days=7) # Date a week before |
|
42 |
+ |
|
43 |
+ current_date_str = current_date.strftime("%d.%m.%Y") |
|
44 |
+ week_before_date_str = week_before_date.strftime("%d.%m.%Y") |
|
45 |
+ |
|
46 |
+ report_name = week_before_date_str + '_' + current_date_str + '_weekly.csv' |
|
47 |
+ report_path = 'reports/weekly/' + report_name |
|
48 |
+ |
|
49 |
+ with open(report_path, 'w') as database: |
|
50 |
+ |
|
51 |
+ headers =['date', 'closed', 'profit'] |
|
52 |
+ csv_writer = csv.DictWriter(database, headers) |
|
53 |
+ |
|
54 |
+ csv_writer.writeheader() |
|
55 |
+ |
|
56 |
+ csv_writer.writerow({'date':current_date_str, 'closed':str(num_closed_spreads), 'profit':str(asset_profit)}) |
|
57 |
+ |
|
58 |
+ try: # Try to send the generated report via email |
|
59 |
+ send.send_weekly_report(report_name, week_before_date_str + '-' + current_date_str) |
|
60 |
+ print('\n| Weekly Report has been Sent\n') |
|
61 |
+ |
|
62 |
+ except FileNotFoundError: |
|
63 |
+ print('[!!!] Cannot send the email: Report does not exists') |
|
64 |
+ |
|
65 |
+ except: |
|
66 |
+ print('[!!!] Cannot send the email: Unexpected Error') |
|
67 |
+ |
|
68 |
+# A function for getting the number of the previous month (ex. dec=12 => return nov=11) |
|
69 |
+def _get_past_month_num(): |
|
70 |
+ current_month_num = int(datetime.datetime.now().strftime("%m")) # Get the current number of a month (1 - 12) |
|
71 |
+ |
|
72 |
+ if current_month_num == 1: |
|
73 |
+ return 12 |
|
74 |
+ else: |
|
75 |
+ return current_month_num - 1 |
|
76 |
+ |
|
77 |
+# A function for constructing the monthly report in a '.csv' file |
|
78 |
+def monthly_report(num_closed_spreads, asset_profit): |
|
79 |
+ current_date = datetime.datetime.now() # Current date |
|
80 |
+ month_before_date = current_date.replace(month=_get_past_month_num()) # Date a month before |
|
81 |
+ |
|
82 |
+ current_date_str = current_date.strftime("%d.%m.%Y") |
|
83 |
+ month_before_date_str = month_before_date.strftime("%b.%Y") |
|
84 |
+ |
|
85 |
+ report_name = month_before_date_str + '_monthly.csv' |
|
86 |
+ report_path = 'reports/monthly/' + report_name |
|
87 |
+ |
|
88 |
+ with open(report_path, 'w') as database: |
|
89 |
+ |
|
90 |
+ headers =['date', 'closed', 'profit'] |
|
91 |
+ csv_writer = csv.DictWriter(database, headers) |
|
92 |
+ |
|
93 |
+ csv_writer.writeheader() |
|
94 |
+ |
|
95 |
+ csv_writer.writerow({'date': current_date_str, 'closed':str(num_closed_spreads), 'profit':str(asset_profit)}) |
|
96 |
+ |
|
97 |
+ try: # Try to send the generated report via email |
|
98 |
+ send.send_monthly_report(report_name, month_before_date_str) |
|
99 |
+ print('\n| Monthly Report has been Sent\n') |
|
100 |
+ |
|
101 |
+ except FileNotFoundError: |
|
102 |
+ print('[!!!] Cannot send the email: Report does not exists') |
|
103 |
+ |
|
104 |
+ except: |
|
105 |
+ print('[!!!] Cannot send the email: Unexpected Error') |
|
106 |
+ |
|
107 |
+# A function for saving all closed BIDS and OFFERS |
|
108 |
+def save_order(type_, coin_price): |
|
109 |
+ current_date_str = datetime.datetime.now().strftime("%d.%m.%Y") # Current date |
|
110 |
+ current_month_str = datetime.datetime.now().strftime("%b") # Current month |
|
111 |
+ current_year_str = datetime.datetime.now().strftime("%Y") # Current year |
|
112 |
+ |
|
113 |
+ try: # Try to create a new file, if it does not exists and write down the info |
|
114 |
+ with open('orders/' + current_month_str + '_' + current_year_str + '_orders.csv', 'x') as database: |
|
115 |
+ |
|
116 |
+ headers =['date', 'pair', 'type', 'price', 'coins'] |
|
117 |
+ csv_writer = csv.DictWriter(database, headers) |
|
118 |
+ |
|
119 |
+ csv_writer.writeheader() |
|
120 |
+ except FileExistsError: |
|
121 |
+ pass |
|
122 |
+ |
|
123 |
+ # However if the file already exists, then open it in 'append' mode |
|
124 |
+ with open('orders/' + current_month_str + '_' + current_year_str + '_orders.csv', 'a') as database: |
|
125 |
+ |
|
126 |
+ headers =['date', 'pair', 'type', 'price', 'coins'] |
|
127 |
+ csv_writer = csv.DictWriter(database, headers) |
|
128 |
+ |
|
129 |
+ csv_writer.writerow({'date': current_date_str, 'pair': bot.TRADING_PAIR, 'type': type_, 'price': str(coin_price), 'coins': bot.COINS_PER_ORDER_TO_PRINT}) |
... | ... |
@@ -0,0 +1,10 @@ |
1 |
+python-binance==0.7.2 |
|
2 |
+requests==2.22.0 |
|
3 |
+autobahn==18.7.1 |
|
4 |
+certifi==2018.4.16 |
|
5 |
+chardet==3.0.4 |
|
6 |
+cryptography==2.3 |
|
7 |
+dateparser==0.7.0 |
|
8 |
+pyOpenSSL==19.0.0 |
|
9 |
+service-identity==17.0.0 |
|
10 |
+Twisted==18.7.0 |
... | ... |
@@ -0,0 +1,123 @@ |
1 |
+#!/usr/bin/env python3 |
|
2 |
+ |
|
3 |
+import smtplib |
|
4 |
+from email.mime.multipart import MIMEMultipart |
|
5 |
+from email.mime.text import MIMEText |
|
6 |
+from email.mime.base import MIMEBase |
|
7 |
+from email import encoders |
|
8 |
+ |
|
9 |
+import bot |
|
10 |
+ |
|
11 |
+# https://www.google.com/settings/security/lesssecureapps |
|
12 |
+# https://accounts.google.com/DisplayUnlockCaptcha |
|
13 |
+ |
|
14 |
+def send_daily_report(report_name_, date_): |
|
15 |
+ mail_content = ''' |
|
16 |
+ Hello, |
|
17 |
+ |
|
18 |
+ This is a Daily Report email. |
|
19 |
+ Find the report document attached. |
|
20 |
+ |
|
21 |
+ Best Regards, |
|
22 |
+ Your Bot |
|
23 |
+ ''' |
|
24 |
+ |
|
25 |
+ message = MIMEMultipart() # instance of MIMEMultipart |
|
26 |
+ message['From'] = bot.SENDER_ADDRESS |
|
27 |
+ message['To'] = bot.RECEIVER_ADDRESS |
|
28 |
+ message['Subject'] = 'Binance bot; ' + bot.TRADING_PAIR + ' (Daily Report: ' + date_ + ')' |
|
29 |
+ |
|
30 |
+ message.attach(MIMEText(mail_content, 'plain')) # attach the body with the msg instance |
|
31 |
+ |
|
32 |
+ attachment = open('reports/daily/' + report_name_, "rb") # open the file to be sent |
|
33 |
+ |
|
34 |
+ payload = MIMEBase('application', 'octet-stream') # instance of MIMEBase and named as 'payload' |
|
35 |
+ payload.set_payload((attachment).read()) # To change the payload into encoded form |
|
36 |
+ |
|
37 |
+ encoders.encode_base64(payload) # encode into base64 |
|
38 |
+ |
|
39 |
+ payload.add_header('Content-Disposition', "attachment; filename= %s" % report_name_) # attach the instance 'payload' to instance 'message' |
|
40 |
+ message.attach(payload) |
|
41 |
+ |
|
42 |
+ session = smtplib.SMTP('smtp.gmail.com', 587) # creates SMTP session |
|
43 |
+ session.starttls() # start TLS for security |
|
44 |
+ session.login(bot.SENDER_ADDRESS, bot.SENDER_PASS) # Authentication |
|
45 |
+ |
|
46 |
+ text = message.as_string() # Converts the Multipart msg into a string |
|
47 |
+ |
|
48 |
+ session.sendmail(bot.SENDER_ADDRESS, bot.RECEIVER_ADDRESS, text) # sending the mail |
|
49 |
+ session.quit() # terminating the session |
|
50 |
+ |
|
51 |
+def send_weekly_report(report_name_, date_): |
|
52 |
+ mail_content = ''' |
|
53 |
+ Hello, |
|
54 |
+ |
|
55 |
+ This is a Weekly Report email. |
|
56 |
+ Find the report document attached. |
|
57 |
+ |
|
58 |
+ Best Regards, |
|
59 |
+ Your Bot |
|
60 |
+ ''' |
|
61 |
+ |
|
62 |
+ message = MIMEMultipart() # instance of MIMEMultipart |
|
63 |
+ message['From'] = bot.SENDER_ADDRESS |
|
64 |
+ message['To'] = bot.RECEIVER_ADDRESS |
|
65 |
+ message['Subject'] = 'Binance bot; ' + bot.TRADING_PAIR + ' (Weekly Report: ' + date_ + ')' |
|
66 |
+ |
|
67 |
+ message.attach(MIMEText(mail_content, 'plain')) # attach the body with the msg instance |
|
68 |
+ |
|
69 |
+ attachment = open('reports/weekly/' + report_name_, "rb") # open the file to be sent |
|
70 |
+ |
|
71 |
+ payload = MIMEBase('application', 'octet-stream') # instance of MIMEBase and named as 'payload' |
|
72 |
+ payload.set_payload((attachment).read()) # To change the payload into encoded form |
|
73 |
+ |
|
74 |
+ encoders.encode_base64(payload) # encode into base64 |
|
75 |
+ |
|
76 |
+ payload.add_header('Content-Disposition', "attachment; filename= %s" % report_name_) # attach the instance 'payload' to instance 'message' |
|
77 |
+ message.attach(payload) |
|
78 |
+ |
|
79 |
+ session = smtplib.SMTP('smtp.gmail.com', 587) # creates SMTP session |
|
80 |
+ session.starttls() # start TLS for security |
|
81 |
+ session.login(bot.SENDER_ADDRESS, bot.SENDER_PASS) # Authentication |
|
82 |
+ |
|
83 |
+ text = message.as_string() # Converts the Multipart msg into a string |
|
84 |
+ |
|
85 |
+ session.sendmail(bot.SENDER_ADDRESS, bot.RECEIVER_ADDRESS, text) # sending the mail |
|
86 |
+ session.quit() # terminating the session |
|
87 |
+ |
|
88 |
+def send_monthly_report(report_name_, date_): |
|
89 |
+ mail_content = ''' |
|
90 |
+ Hello, |
|
91 |
+ |
|
92 |
+ This is a Monthly Report email. |
|
93 |
+ Find the report document attached. |
|
94 |
+ |
|
95 |
+ Best Regards, |
|
96 |
+ Your Bot |
|
97 |
+ ''' |
|
98 |
+ |
|
99 |
+ message = MIMEMultipart() # instance of MIMEMultipart |
|
100 |
+ message['From'] = bot.SENDER_ADDRESS |
|
101 |
+ message['To'] = bot.RECEIVER_ADDRESS |
|
102 |
+ message['Subject'] = 'Binance bot; ' + bot.TRADING_PAIR + ' (Monthly Report: ' + date_ + ')' |
|
103 |
+ |
|
104 |
+ message.attach(MIMEText(mail_content, 'plain')) # attach the body with the msg instance |
|
105 |
+ |
|
106 |
+ attachment = open('reports/monthly/' + report_name_, "rb") # open the file to be sent |
|
107 |
+ |
|
108 |
+ payload = MIMEBase('application', 'octet-stream') # instance of MIMEBase and named as 'payload' |
|
109 |
+ payload.set_payload((attachment).read()) # To change the payload into encoded form |
|
110 |
+ |
|
111 |
+ encoders.encode_base64(payload) # encode into base64 |
|
112 |
+ |
|
113 |
+ payload.add_header('Content-Disposition', "attachment; filename= %s" % report_name_) # attach the instance 'payload' to instance 'message' |
|
114 |
+ message.attach(payload) |
|
115 |
+ |
|
116 |
+ session = smtplib.SMTP('smtp.gmail.com', 587) # creates SMTP session |
|
117 |
+ session.starttls() # start TLS for security |
|
118 |
+ session.login(bot.SENDER_ADDRESS, bot.SENDER_PASS) # Authentication |
|
119 |
+ |
|
120 |
+ text = message.as_string() # Converts the Multipart msg into a string |
|
121 |
+ |
|
122 |
+ session.sendmail(bot.SENDER_ADDRESS, bot.RECEIVER_ADDRESS, text) # sending the mail |
|
123 |
+ session.quit() # terminating the session |