operations_research/OR_HTML_04/programme/Bot_Allokation.py
dschlueter b7af2f1d9a Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.

Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.

Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.

Zusaetzlich in diesem Commit:

* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
  figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
  unveraendert daneben bestehen. ortools steht in der Grundausstattung,
  highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
  schlanken Installation gar nicht erst auftreten.

* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
  importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
  steht in requirements.txt, wird aber von keinem Programm importiert,
  sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
  [empfehlungen]).

* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
  ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
  Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.

* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
  tatsaechlichen aus erzeuge_titelseite.py korrigiert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00

96 lines
3.8 KiB
Python

#!/usr/bin/env python3
# Bot_Allokation.py
"""
Kapitel Einfuehrung: Erstes Optimierungsmodell mit Google OR-Tools (CP-SAT).
Problem: Server-Allokation für Trading-Bots.
Modell mit expliziter Nebenbedingung statt versteckter Variablengrenze,
vollständiger Statusauswertung und Ergebnisprüfung.
"""
from ortools.sat.python import cp_model
# --- Parameter (die "Fakten" des Problems) ---------------------------------
GEWINN_A, GEWINN_B = 150, 250 # Euro pro Bot und Tag
CPU_A, CPU_B = 2, 5 # vCPU-Bedarf je Bot
RAM_A, RAM_B = 4, 6 # GB Arbeitsspeicher je Bot
CPU_GESAMT, RAM_GESAMT = 40, 60 # verfügbare Kapazitäten
MAX_ARBITRAGE = 8 # Marktliquiditätsgrenze
def loese_bot_allokation(zeitlimit_s: float = 10.0):
"""Baut das Modell, löst es und gibt eine Auswertung aus."""
# 1. Modell instanziieren
modell = cp_model.CpModel()
# 2. Entscheidungsvariablen anlegen
# NewIntVar(untere_grenze, obere_grenze, name)
# Die obere Grenze ist bewusst großzügig; die echte Beschränkung
# formulieren wir unten als Nebenbedingung, damit das Modell die
# mathematische Formulierung 1:1 abbildet.
x_a = modell.NewIntVar(0, 100, "Arbitrage_Bots")
x_b = modell.NewIntVar(0, 100, "Trend_Bots")
# 3. Nebenbedingungen definieren
c_cpu = modell.Add(CPU_A * x_a + CPU_B * x_b <= CPU_GESAMT) # vCPU-Limit
c_ram = modell.Add(RAM_A * x_a + RAM_B * x_b <= RAM_GESAMT) # RAM-Limit
c_liq = modell.Add(x_a <= MAX_ARBITRAGE) # Marktliquidität
# 4. Zielfunktion: Maximiere den Tagesgewinn
modell.Maximize(GEWINN_A * x_a + GEWINN_B * x_b)
# 5. Solver konfigurieren und ausführen
loeser = cp_model.CpSolver()
loeser.parameters.max_time_in_seconds = zeitlimit_s
status = loeser.Solve(modell)
# 6. Status auswerten -- IMMER alle Fälle behandeln
status_text = loeser.StatusName(status)
if status not in (cp_model.OPTIMAL, cp_model.FEASIBLE):
print(f"Keine verwertbare Lösung. Solver-Status: {status_text}")
if status == cp_model.INFEASIBLE:
print("Das Modell ist unlösbar - die Bedingungen widersprechen sich.")
return None
a, b = loeser.Value(x_a), loeser.Value(x_b)
gewinn = loeser.ObjectiveValue()
# 7. Ergebnis ausgeben
print("=" * 58)
print(" OPTIMALE BOT-ALLOKATION")
print("=" * 58)
print(f"Solver-Status: {status_text}"
f"{' (beweisbar optimal)' if status == cp_model.OPTIMAL else ' (zulässig, nicht bewiesen)'}")
print(f"Arbitrage-Bots (x_A): {a} Instanzen")
print(f"Trendfolge-Bots (x_B): {b} Instanzen")
print(f"Täglicher Max-Gewinn: {gewinn:,.2f} EUR")
genutzt_cpu = CPU_A * a + CPU_B * b
genutzt_ram = RAM_A * a + RAM_B * b
print("-" * 58)
print(f"CPU-Auslastung: {genutzt_cpu} / {CPU_GESAMT} vCPUs "
f"({genutzt_cpu / CPU_GESAMT * 100:.1f} %)")
print(f"RAM-Auslastung: {genutzt_ram} / {RAM_GESAMT} GB "
f"({genutzt_ram / RAM_GESAMT * 100:.1f} %)")
print(f"Arbitrage-Limit: {a} / {MAX_ARBITRAGE}")
# 8. Selbstkontrolle: Sind wirklich alle Bedingungen eingehalten?
assert genutzt_cpu <= CPU_GESAMT, "vCPU-Limit verletzt!"
assert genutzt_ram <= RAM_GESAMT, "RAM-Limit verletzt!"
assert a <= MAX_ARBITRAGE, "Arbitrage-Limit verletzt!"
# 9. Zum Vergleich: die naive Strategie "nur der lukrativere Bot"
naiv_b = min(CPU_GESAMT // CPU_B, RAM_GESAMT // RAM_B)
naiv_gewinn = GEWINN_B * naiv_b
print("-" * 58)
print(f"Naive Strategie (nur Trendfolge): {naiv_b} Bots -> {naiv_gewinn:,.2f} EUR")
print(f"Vorteil der Optimierung: {gewinn - naiv_gewinn:,.2f} EUR pro Tag "
f"({(gewinn / naiv_gewinn - 1) * 100:.1f} %)")
print("=" * 58)
return a, b, gewinn
if __name__ == "__main__":
loese_bot_allokation()