The DWZ Rating System: German Engineering for Chess Ratings

Our tour of rating systems continues! So far, we’ve covered Elo, Glicko-1, Glicko-2, TrueSkill, and the ECF system. Now it’s time to explore another national chess rating system: the Deutsche Wertungszahl (DWZ), or German Evaluation Number.

Like many things German, the DWZ system combines precision engineering with practical considerations to create something both effective and efficient.

The Origin Story: German Precision in Rating Design

The DWZ system was developed by the German Chess Federation (Deutscher Schachbund) and was introduced nationwide on January 1, 1993, following German reunification. It replaced the Ingo-System used in West Germany and the NWZ-System used in East Germany. While it draws inspiration from the Elo system, it incorporates several unique features that reflect a distinctly German approach to the rating problem.

The system was designed with several goals in mind:

  1. Provide accurate ratings for players of all skill levels
  2. Adapt quickly to performance changes, especially for young or improving players
  3. Maintain stability for established players
  4. Account for the number of games played
  5. Be resistant to rating inflation

What makes the DWZ system particularly interesting is its age-dependent development coefficient, which recognizes that younger players’ skills tend to improve more rapidly than those of established players.

How the DWZ System Works: Age-Aware Ratings

The DWZ system is based on the Elo concept but adds several refinements:

  1. Age-Dependent Development Coefficient: The rate at which ratings change depends on the player’s age, with younger players’ ratings changing more quickly
  2. Performance-Based Updates: Ratings are updated based on the difference between expected and actual performance
  3. Game Count Consideration: The number of games a player has played affects how quickly their rating changes
  4. Acceleration for Underrated Players: Players who significantly outperform their rating receive larger rating increases

The key formulas involve:

Expected Score = 1 / (1 + 10^((Opponent Rating - Player Rating) / 400))

This is similar to Elo, but the rating update formula is more complex:

New Rating = Old Rating + Development Coefficient * (Actual Score - Expected Score)

Where the Development Coefficient depends on:

  • The player’s age
  • Their current rating
  • The number of games they’ve played
  • How much they outperformed expectations

Implementing DWZ with Elote

Let’s see how the DWZ system works in practice with Elote:

from elote import DWZCompetitor

# Initialize two competitors with default rating of 1500
player_a = DWZCompetitor()
player_b = DWZCompetitor()

# Print initial ratings
print(f"Initial ratings - Player A: {player_a.rating}")
print(f"Initial ratings - Player B: {player_b.rating}")

# Player A wins against Player B
player_a.beat(player_b)

# Print updated ratings
print(f"After Player A beats Player B - Player A: {player_a.rating}")
print(f"After Player A beats Player B - Player B: {player_b.rating}")

Now let’s demonstrate the age factor in DWZ:

# Initialize players with different ages
young_player = DWZCompetitor(initial_rating=1500, age=14)  # Teenager
veteran = DWZCompetitor(initial_rating=1500, age=45)  # Middle-aged player

# Check expected outcome
print(f"Expected outcome: {young_player.expected_score(veteran):.4f}")

# Let's say the young player wins several games
for _ in range(5):
    young_player.beat(veteran)

# See how ratings changed
print(f"Young player new rating: {young_player.rating}")
print(f"Veteran new rating: {veteran.rating}")

# Now let's say the veteran wins several games
for _ in range(5):
    veteran.beat(young_player)

# See the updated ratings
print(f"Young player final rating: {young_player.rating}")
print(f"Veteran final rating: {veteran.rating}")

Notice how the young player’s rating changes more dramatically than the veteran’s, even though they have the same win-loss record. This is the age-dependent development coefficient at work.

Visualizing DWZ: Age Factor and Rating Changes

Let’s visualize some key aspects of the DWZ system:

import matplotlib.pyplot as plt
import numpy as np
import os
from elote import DWZCompetitor, EloCompetitor

# Visualize how age affects rating changes
ages = np.arange(10, 81, 5)
rating_changes_win = []
rating_changes_loss = []

for age in ages:
    # Create a player of this age
    player = DWZCompetitor(initial_rating=1500, age=age)
    opponent = DWZCompetitor(initial_rating=1500, age=30)  # Fixed opponent
    
    # Record rating before match
    initial_rating = player.rating
    
    # Simulate a win
    player.beat(opponent)
    rating_changes_win.append(player.rating - initial_rating)
    
    # Reset and simulate a loss
    player = DWZCompetitor(initial_rating=1500, age=age)
    opponent = DWZCompetitor(initial_rating=1500, age=30)
    initial_rating = player.rating
    opponent.beat(player)
    rating_changes_loss.append(player.rating - initial_rating)

# Plot the results
plt.figure(figsize=(10, 6))
plt.plot(ages, rating_changes_win, 'g-', label='Rating Change After Win')
plt.plot(ages, rating_changes_loss, 'r-', label='Rating Change After Loss')
plt.axhline(y=0, color='k', linestyle='-', alpha=0.3)
plt.grid(True, alpha=0.3)
plt.xlabel('Player Age')
plt.ylabel('Rating Change')
plt.title('DWZ Rating Change vs. Player Age')
plt.legend()

# Save the figure
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
output_dir = os.path.join(project_root, 'static', 'images', 'elote', '06-dwz-rating-system')
os.makedirs(output_dir, exist_ok=True)
plt.savefig(os.path.join(output_dir, 'age_factor.png'))

Age Factor

This chart shows how a player’s age affects rating changes in the DWZ system. Notice how younger players experience much larger rating changes (both gains and losses) compared to older players. This reflects the system’s recognition that younger players’ skills are still developing rapidly.

# Visualize win probability based on rating difference
rating_diffs = np.arange(-400, 401, 20)
win_probs = []

for diff in rating_diffs:
    player = DWZCompetitor(initial_rating=1500)
    opponent = DWZCompetitor(initial_rating=1500 + diff)
    win_probs.append(player.expected_score(opponent))

plt.figure(figsize=(10, 6))
plt.plot(rating_diffs, win_probs)
plt.axhline(y=0.5, color='r', linestyle='--', alpha=0.5)
plt.axvline(x=0, color='r', linestyle='--', alpha=0.5)
plt.grid(True, alpha=0.3)
plt.xlabel('Rating Difference (Player - Opponent)')
plt.ylabel('Win Probability')
plt.title('DWZ Win Probability vs. Rating Difference')

# Save the figure
plt.savefig(os.path.join(output_dir, 'win_probability.png'))

Win Probability

This chart shows the win probability based on rating difference, which follows the same logistic curve as Elo. A player with a 200-point advantage is expected to win about 75% of the time.

# Compare DWZ and Elo rating changes
rating_diffs = np.arange(-400, 401, 20)
dwz_changes_young = []
dwz_changes_old = []
elo_changes = []

for diff in rating_diffs:
    # DWZ young player
    young_player = DWZCompetitor(initial_rating=1500, age=14)
    opponent_young = DWZCompetitor(initial_rating=1500 + diff)
    initial_rating_young = young_player.rating
    young_player.beat(opponent_young)
    dwz_changes_young.append(young_player.rating - initial_rating_young)
    
    # DWZ older player
    old_player = DWZCompetitor(initial_rating=1500, age=60)
    opponent_old = DWZCompetitor(initial_rating=1500 + diff)
    initial_rating_old = old_player.rating
    old_player.beat(opponent_old)
    dwz_changes_old.append(old_player.rating - initial_rating_old)
    
    # Elo player
    elo_player = EloCompetitor(initial_rating=1500)
    elo_opponent = EloCompetitor(initial_rating=1500 + diff)
    initial_rating_elo = elo_player.rating
    elo_player.beat(elo_opponent)
    elo_changes.append(elo_player.rating - initial_rating_elo)

plt.figure(figsize=(10, 6))
plt.plot(rating_diffs, dwz_changes_young, 'g-', label='DWZ (Age 14)')
plt.plot(rating_diffs, dwz_changes_old, 'b-', label='DWZ (Age 60)')
plt.plot(rating_diffs, elo_changes, 'r-', label='Elo')
plt.axhline(y=0, color='k', linestyle='-', alpha=0.3)
plt.axvline(x=0, color='k', linestyle='-', alpha=0.3)
plt.grid(True, alpha=0.3)
plt.xlabel('Rating Difference (Opponent - Player)')
plt.ylabel('Rating Change After Win')
plt.title('Rating Change Comparison: DWZ vs Elo')
plt.legend()

# Save the figure
plt.savefig(os.path.join(output_dir, 'elo_vs_dwz.png'))

Elo vs DWZ

This comparison shows how DWZ rating changes differ from Elo. Notice that young players (age 14) in the DWZ system experience much larger rating changes than both older DWZ players and Elo players, especially when they beat much stronger opponents.

# Visualize DWZ rating changes for different initial ratings
initial_ratings = np.arange(1000, 2501, 100)
rating_changes = []

for rating in initial_ratings:
    player = DWZCompetitor(initial_rating=rating, age=30)
    opponent = DWZCompetitor(initial_rating=1500)  # Fixed opponent
    initial_player_rating = player.rating
    player.beat(opponent)
    rating_changes.append(player.rating - initial_player_rating)

plt.figure(figsize=(10, 6))
plt.plot(initial_ratings, rating_changes)
plt.axhline(y=0, color='r', linestyle='--', alpha=0.5)
plt.grid(True, alpha=0.3)
plt.xlabel('Initial Rating')
plt.ylabel('Rating Change After Beating 1500-Rated Opponent')
plt.title('DWZ Rating Change vs. Initial Rating')

# Save the figure
plt.savefig(os.path.join(output_dir, 'dwz_rating_changes.png'))

DWZ Rating Changes

This chart shows how a player’s initial rating affects the rating change after beating a 1500-rated opponent. Higher-rated players gain fewer points for the same victory, reflecting the system’s expectation that they should win against lower-rated opponents.

Real-World Example: A Junior Chess Tournament

Let’s use the DWZ system to simulate a junior chess tournament, where players of different ages compete:

import random

# Create players of different ages
players = {
    "Alex (12)": DWZCompetitor(initial_rating=1400, age=12),
    "Ben (14)": DWZCompetitor(initial_rating=1450, age=14),
    "Charlie (16)": DWZCompetitor(initial_rating=1500, age=16),
    "Dana (18)": DWZCompetitor(initial_rating=1550, age=18),
    "Eli (20)": DWZCompetitor(initial_rating=1600, age=20),
    "Fiona (22)": DWZCompetitor(initial_rating=1650, age=22)
}

# Function to simulate a match with some randomness
def simulate_match(player1, player2, player1_name, player2_name):
    # Calculate win probability using DWZ formula (similar to Elo)
    win_prob = player1.expected_score(player2)
    
    # Determine outcome with randomness
    if random.random() < win_prob:
        player1.beat(player2)
        return f"{player1_name} beats {player2_name}"
    else:
        player2.beat(player1)
        return f"{player2_name} beats {player1_name}"

# Track rating history
rating_history = {name: [player.rating] for name, player in players.items()}
match_count = 0

# Simulate 100 matches
for _ in range(100):
    # Randomly select two different players
    player_names = random.sample(list(players.keys()), 2)
    player1_name, player2_name = player_names
    
    # Simulate match
    result = simulate_match(players[player1_name], players[player2_name], player1_name, player2_name)
    match_count += 1
    
    # Record ratings after each match
    for name, player in players.items():
        rating_history[name].append(player.rating)
    
    # Print every 20 matches
    if match_count % 20 == 0:
        print(f"After {match_count} matches:")
        for name, player in players.items():
            print(f"{name}: Rating = {player.rating}")

This simulation demonstrates how the DWZ system handles players of different ages in a tournament setting. The younger players’ ratings will change more dramatically than the older players’, even with similar win-loss records.

Pros and Cons of the DWZ System

Pros:

  • Age-aware: Recognizes that younger players improve faster
  • Adaptive: Adjusts rating changes based on performance history
  • Stable for veterans: Prevents wild rating swings for established players
  • Inflation resistant: Design features help prevent rating inflation over time
  • Practical: Designed for real-world tournament use

Cons:

  • Complex: More parameters and calculations than Elo
  • Requires age data: Need to track player ages
  • Less intuitive: Harder to explain to casual players
  • National focus: Designed specifically for German chess, may not generalize well
  • Limited adoption: Not as widely used as Elo or FIDE ratings

When to Use DWZ

The DWZ system is best suited for:

  • Chess clubs and tournaments with players of varying ages
  • Rating systems where you want to account for player development
  • Situations where you need to balance rating stability with adaptability
  • Communities where you have reliable age data for participants

Conclusion: Age-Aware Rating Systems

The DWZ system reminds us that one-size-fits-all approaches to ratings have limitations. By recognizing that players of different ages develop at different rates, the DWZ system provides a more nuanced approach to rating chess players.

While it may be more complex than Elo, the DWZ system offers valuable insights into how we might design rating systems that account for player development over time. Whether you’re rating chess players, video game competitors, or any other competitive activity, considering the age and experience of participants can lead to more accurate and fair ratings.

In our next post, we’ll explore the Colley Matrix method, a completely different approach to ratings that doesn’t rely on pairwise comparisons at all!