Вопрос или проблема
Я пытаюсь создать аккуратную торговую среду, но когда я убиваю всё население при определённых условиях, возникает эта ошибка, и эволюция не продолжается.
Ошибка:
Traceback (most recent call last):
File "C:\Users\Administrator\TradEvolver\main.py", line 188, in <module>
run("./config.txt")
File "C:\Users\Administrator\TradEvolver\main.py", line 186, in run
winner = p.run(main, 1000)
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python310\lib\site-packages\neat\population.py", line 94, in run
if best is None or g.fitness > best.fitness:
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python310\lib\site-packages\pandas\core\generic.py", line 1577, in __nonzero__
raise ValueError(
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
Код:
import pandas as pd
import random
import time
import matplotlib.pyplot as plt
import threading
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk
from tkinter import messagebox
import sys
import neat
def calculate_profit(buy_price, sell_price, balance):
c = 0.001
profit = (sell_price - buy_price) / buy_price * balance - (balance * c)
new_balance = balance + profit
return new_balance
class Trader:
def __init__(self):
self.balance = 10000
self.buy_price = 0
self.since_last_transaction = 0
self.consq = []
self.consq2 = []
def buy(self, buy_price):
self.buy_price = buy_price
def sell(self, sell_price):
self.balance = calculate_profit(self.buy_price, sell_price, self.balance)
self.buy_price = 0
file = "./data/BTCUSDT_2015-01-01T00_00_00+00_00__2024-10-10T23_59_59+00_00.csv"
df = pd.read_csv(file)
root = tk.Tk()
root.title("TradEvolver - Evolution Tracker")
def on_close():
if messagebox.askyesno("Подтверждение выхода", "Вы уверены, что хотите закрыть программу?"):
root.quit()
sys.exit()
root.protocol("WM_DELETE_WINDOW", on_close)
traders = []
for i in range(10):
traders.append(Trader())
close_data = []
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
fig, ax = plt.subplots()
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().grid(row=0, column=0, sticky="nsew")
balance_frame = tk.Frame(root)
balance_frame.grid(row=1, column=0, sticky="nsew")
balance_labels = []
def update_plot():
for close in df['close']:
close_data.append(close)
ax.clear()
ax.plot(close_data, color="blue")
ax.set_title('TradEvolver - Evolution Tracker')
ax.set_xlabel('Время')
ax.set_ylabel('Цена закрытия')
for i, trader in enumerate(traders):
action = random.choice(['buy', 'sell', 'hold'])
if action == 'buy' and trader.buy_price == 0 :
trader.buy(close)
elif action == 'sell' and trader.buy_price != 0:
trader.sell(close)
balance_labels[i].config(text=f"Баланс: {trader.balance:.2f}")
canvas.draw()
#time.sleep(0.01)
def start_plotting():
plot_thread = threading.Thread(target=update_plot)
plot_thread.start()
root.mainloop()
def main(genomes, config):
nets = []
ge = []
traders = []
for _, g in genomes:
net = neat.nn.FeedForwardNetwork.create(g, config)
nets.append(net)
traders.append(Trader())
g.fitness = 0
ge.append(g)
for trader in traders:
label = tk.Label(balance_frame, text=f"Баланс: {trader.balance:.2f}")
label.pack()
balance_labels.append(label)
for x, close in enumerate(df['close']):
close_data.append(close)
ax.clear()
ax.plot(close_data, color="blue")
ax.set_title('TradEvolver - Evolution Tracker')
ax.set_xlabel('Время')
ax.set_ylabel('Цена закрытия')
canvas.draw()
if x > 7 and len(traders) > 0:
for i, trader in enumerate(traders):
istooconseq = False
ehe = 1 if close > df["close"][x - 7] else 0
output = nets[i].activate((close, trader.buy_price, df["close"][x] - df["open"][x], df["volume"][x], ehe))
action = output.index(max(output))
if action == 0 and trader.buy_price == 0: # Купить
trader.buy(close)
trader.since_last_transaction = 0
trader.consq2.append(0)
elif action == 1: #Держать
trader.since_last_transaction += 1
trader.consq2.append(0)
elif action == 2 and trader.buy_price != 0: # Продать
trader.since_last_transaction = 0
trader.sell(close)
trader.consq2.append(1)
else:
trader.consq2.append(0)
trader.since_last_transaction += 1
if trader.buy_price != 0:
ge[i].fitness += ((df["close"] - df["open"])/df["open"])*100
max_index, max_trader = max(enumerate(traders), key=lambda x: x[1].balance)
#print(max_trader.balance, max_trader.since_last_transaction, max_trader.buy_price)
if x > 150:
trader.consq = trader.consq2[x-150:x]
istooconseq = trader.consq.count(1) > 60
#print(trader.consq.count(1))
if trader.since_last_transaction > 250 or trader.balance < 5500 or istooconseq:
traders.pop(i)
ge.pop(i)
nets.pop(i)
if trader.balance < 5500:
print("слишком низкий баланс", i, trader.balance)
elif trader.since_last_transaction > 250:
print("не делает сделок", i, trader.since_last_transaction)
elif istooconseq:
print("слишком много последовательных", i, trader.consq.count(1))
istooconseq = False
# balance_labels[i].config(text=f"Баланс: {trader.balance:.2f}")
canvas.draw()
if len(traders) == 0:
break
def run(config_path):
config = neat.config.Config(neat.DefaultGenome, neat.DefaultReproduction, neat.DefaultSpeciesSet, neat.DefaultStagnation, config_path)
p = neat.Population(config)
p.add_reporter(neat.StdOutReporter(True))
stats = neat.StatisticsReporter()
p.add_reporter(stats)
winner = p.run(main, 1000)
run("./config.txt")
То, что я не понял, так это то, что я тестировал аналогичный алгоритм NEAT для flippybird, и когда птица сталкивается с трубой, она погибала. Вот код
import pygame
import random
import neat
import os
import time
pygame.font.init()
WIN_WIDTH = 500
WIN_HEIGHT = 800
BIRD_IMGS = [pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bird1.png"))), pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bird2.png"))), pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bird3.png")))]
PIPE_IMG = pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "pipe.png")))
BASE_IMG = pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "base.png")))
BG_IMG = pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bg.png")))
STAT_FONT = pygame.font.SysFont("comicsans", 50)
class Bird:
IMGS = BIRD_IMGS
MAX_ROTATION = 25
ROT_VEL = 20
ANIMATION_TIME = 5
def __init__(self, x, y):
self.x = x
self.y = y
self.tilt = 0
self.tick_count = 0
self.vel = 0
self.height = self.y
self.img_count = 0
self.img = self.IMGS[0]
def jump(self):
self.vel = -10.5
self.tick_count = 0
self.height = self.y
def move(self):
self.tick_count += 1
d = self.vel*self.tick_count + 1.5*self.tick_count**2
if d >= 16:
d = 16
if d < 0:
d -= 2
self.y = self.y + d
if d < 0 or self.y < self.height + 50:
if self.tilt < self.MAX_ROTATION:
self.tilt = self.MAX_ROTATION
else:
if self.tilt > -90:
self.tilt -= self.ROT_VEL
def draw(self, win):
self.img_count += 1
if self.img_count < self.ANIMATION_TIME:
self.img = self.IMGS[0]
elif self.img_count < self.ANIMATION_TIME*2:
self.img = self.IMGS[1]
elif self.img_count < self.ANIMATION_TIME*3:
self.img = self.IMGS[2]
elif self.img_count < self.ANIMATION_TIME*4:
self.img = self.IMGS[1]
elif self.img_count == self.ANIMATION_TIME*4 + 1:
self.img = self.IMGS[0]
self.img_count = 0
if self.tilt <= -80:
self.img = self.IMGS[1]
self.img_count = self.ANIMATION_TIME*2
rotated_image = pygame.transform.rotate(self.img, self.tilt)
new_rect = rotated_image.get_rect(center=self.img.get_rect(topleft = (self.x, self.y)).center)
win.blit(rotated_image, new_rect.topleft)
def get_mask(self):
return pygame.mask.from_surface(self.img)
class Pipe:
GAP = 200
VEL = 10
def __init__(self, x):
self.x = x
self.height = 0
self.top = 0
self.bottom = 0
self.PIPE_TOP = pygame.transform.flip(PIPE_IMG, False, True)
self.PIPE_BOTTOM = PIPE_IMG
self.passed = False
self.set_height()
def set_height(self):
self.height = random.randrange(50, 450)
self.top = self.height - self.PIPE_TOP.get_height()
self.bottom = self.height + self.GAP
def move(self):
self.x -= self.VEL
def draw(self, win):
win.blit(self.PIPE_TOP, (self.x, self.top))
win.blit(self.PIPE_BOTTOM, (self.x, self.bottom))
def collide(self, bird):
bird_mask = bird.get_mask()
top_mask = pygame.mask.from_surface(self.PIPE_TOP)
bottom_mask = pygame.mask.from_surface(self.PIPE_BOTTOM)
top_offset = (self.x - bird.x, self.top - round(bird.y))
bottom_offset = (self.x - bird.x, self.bottom - round(bird.y))
b_point = bird_mask.overlap(bottom_mask, bottom_offset)
t_point = bird_mask.overlap(top_mask, top_offset)
if t_point or b_point:
return True
return False
class Base:
VEL = 5
WIDTH = BASE_IMG.get_width()
IMG = BASE_IMG
def __init__(self, y):
self.y = y
self.x1 = 0
self.x2 = self.WIDTH
def move(self):
self.x1 -= self.VEL
self.x2 -= self.VEL
if self.x1 + self.WIDTH < 0:
self.x1 = self.x2 + self.WIDTH
if self.x2 + self.WIDTH < 0:
self.x2 = self.x1 + self.WIDTH
def draw(self, win):
win.blit(self.IMG, (self.x1, self.y))
win.blit(self.IMG, (self.x2, self.y))
def draw_window(win, birds, pipes, base, score):
win.blit(BG_IMG, (0,0))
for pipe in pipes:
pipe.draw(win)
text = STAT_FONT.render("Счет: " + str(score), 1, (255,255,255))
win.blit(text, (WIN_WIDTH -10 - text.get_width(), 10))
base.draw(win)
for bird in birds:
bird.draw(win)
pygame.display.update()
def main(genomes, config):
nets = []
ge = []
birds = []
for _, g in genomes:
net = neat.nn.FeedForwardNetwork.create(g, config)
nets.append(net)
birds.append(Bird(230,350))
g.fitness = 0
ge.append(g)
base = Base(730)
pipes = [Pipe(600)]
win = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT))
run = True
clock = pygame.time.Clock()
score = 0
while run:
clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.quit()
quit()
pipe_ind = 0
if len(birds) > 0:
if len(pipes) > 1 and birds[0].x > pipes[0].x + pipes[0].PIPE_TOP.get_width():
pipe_ind = 1
else:
run = False
break
for x, bird in enumerate(birds):
bird.move()
ge[x].fitness += 0.1
output = nets[x].activate((bird.y, abs(bird.y - pipes[pipe_ind].height), abs(bird.y - pipes[pipe_ind].bottom)))
if output[0] > 0.5:
bird.jump()
#bird.move()
rem = []
add_pipe = False
for pipe in pipes:
for x, bird in enumerate(birds):
if pipe.collide(bird):
ge[x].fitness -= 1
birds.pop(x)
nets.pop(x)
ge.pop(x)
if not pipe.passed and pipe.x < bird.x:
pipe.passed = True
add_pipe = True
if pipe.x + pipe.PIPE_TOP.get_width() < 0:
rem.append(pipe)
pipe.move()
if add_pipe:
score += 1
for g in ge:
g.fitness += 5
pipes.append(Pipe(600))
for r in rem:
pipes.remove(r)
for x, bird in enumerate(birds):
if bird.y + bird.img.get_height() >= 730 or bird.y < 0:
birds.pop(x)
nets.pop(x)
ge.pop(x)
base.move()
draw_window(win, birds, pipes, base, score)
def run(config_path):
config = neat.config.Config(neat.DefaultGenome, neat.DefaultReproduction, neat.DefaultSpeciesSet, neat.DefaultStagnation, config_path)
p = neat.Population(config)
p.add_reporter(neat.StdOutReporter(True))
stats = neat.StatisticsReporter()
p.add_reporter(stats)
winner = p.run(main, 50)
if __name__ == "__main__":
local_dir = os.path.dirname(__file__)
config_path = os.path.join(local_dir, "config.txt")
run(config_path)
В основном, я пытался реализовать такую же логику, но возникает ошибка в моей среде, тогда как алгоритм flippybird работает идеально.
Ответ или решение
Ошибка в NEAT Python после удаления всей популяции: решение и объяснение
Введение
Проблема, с которой вы столкнулись, имеет отношение к обработке популяции в вашем NEAT алгоритме для торговли. Ошибка, указанная в вашем сообщении (ValueError: The truth value of a Series is ambiguous...
), возникает, когда код пытается выполнить логическую операцию на объекте, который не может быть интерпретирован как истинный или ложный. Это случается, если объекты, с которыми вы работаете, являются недопустимыми или имеют неожиданное состояние.
Анализ ошибки
Ваша ошибка происходит в следующем фрагменте кода:
if best is None or g.fitness > best.fitness:
На данном этапе best
должен содержать лучшую геномную структуру, но когда у вас нет популяции (например, после удаления всех трейдеров), best
может оставаться неопределенным результатом или содержать некорректные данные, такие как пустой объект Series
из pandas, что и приводит к сбою.
Причины возникновения проблемы
-
Удаление всех трейдеров: Когда вы удаляете всех трейдеров из списка
traders
, а также из списков нейронных сетей (nets
) и геномов (ge
), вы можете избежать дальнейшей обработки, вызываяbreak
в вашем коде. Однако функция, которая продолжает выполнение, возможно, не ожидает, что списки окажутся пустыми. -
Необходимость проверки состояния популяции: Необходимо добавить проверки состояния прежде, чем пытаться оценить или получить доступ к элементам списков, которые могут быть пустыми.
Решение проблемы
Для решения вашей проблемы, вам стоит добавить дополнительные проверки и логические условия в вашем коде. Ниже приведены модификации, которые помогут избежать возникновения ошибки:
-
Проверка и обработка пустой популяции:
Внесите изменения в цикл, который удаляет трейдеров:if len(traders) == 0: print("Все трейдеры удалены, завершаем эволюцию.") break
Перед тем, как вызывать
g.fitness > best.fitness
, убедитесь, чтоge
не пуст:if len(ge) > 0: # Здесь ваш код для обработки фитнеса else: print("Список геномов пуст, прекращаем текущую итерацию.") continue
-
Обработка геномов в
main
функции:
Убедитесь, что так же как вы проверяете на наличие трейдеров, вы также проверяете, есть ли еще геномы, с которыми нужно работать. Если списокge
пуст, завершайте функцию нормально. -
Исключение микро-управлений:
Пересмотрите логику вашего кода, чтобы избежать обращения к пустым или неопределенным переменным, которые могут привести к ошибкам.
Заключение
Ошибки, связанные с пустыми списками и объектами, являются обычным делом в разработке на Python, особенно в контексте сложных систем, таких как NEAT. Будьте внимательны к состоянию вашей популяции и не забывайте проверять списки на пустоту перед выполнением операций, которые могут вызвать ошибки. С применением предложенных модификаций ваш алгоритм должен начать работать корректно без неожиданных сбоев.