rrt/crates/rrt-runtime/src/derived/finance/credit_rating.rs

151 lines
5.2 KiB
Rust

use crate::derived::{
runtime_company_annual_finance_state, runtime_company_average_live_bond_coupon,
runtime_company_control_transfer_stat_value_f64, runtime_company_derived_stat_value,
runtime_round_f64_to_i64, runtime_world_issue_opinion_term_sum_raw,
runtime_world_prime_rate_baseline,
};
use crate::event::metrics::{
RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, RUNTIME_WORLD_ISSUE_CREDIT_MARKET,
RUNTIME_WORLD_ISSUE_PRIME_RATE,
};
use crate::state::RuntimeState;
pub fn runtime_company_credit_rating(state: &RuntimeState, company_id: u32) -> Option<i64> {
let company = state
.companies
.iter()
.find(|company| company.company_id == company_id)?;
if let Some(credit_rating_score) = company.credit_rating_score {
return Some(credit_rating_score);
}
let annual_finance_state = runtime_company_annual_finance_state(state, company_id)?;
if annual_finance_state.outstanding_shares == 0 {
return Some(-512);
}
let mut weighted_recent_profit_total = 0.0f64;
let mut weighted_recent_profit_weight = 0.0f64;
for (index, (net_profit, fuel_cost)) in annual_finance_state
.trailing_full_year_net_profits
.iter()
.zip(annual_finance_state.trailing_full_year_fuel_costs.iter())
.take(4)
.enumerate()
{
let weight = (4 - index) as f64;
weighted_recent_profit_total += (*net_profit - *fuel_cost) as f64 * weight;
weighted_recent_profit_weight += weight;
}
let weighted_recent_profit = if weighted_recent_profit_weight > 0.0 {
weighted_recent_profit_total / weighted_recent_profit_weight
} else {
0.0
};
let current_slot_12 = runtime_company_control_transfer_stat_value_f64(state, company_id, 0x12)?;
let current_slot_30 = runtime_company_derived_stat_value(
state,
company_id,
RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
0x30,
)?;
let current_slot_31 = runtime_company_derived_stat_value(
state,
company_id,
RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
0x31,
)?;
let average_live_bond_coupon = runtime_company_average_live_bond_coupon(state, company_id)?;
let mut finance_pressure = average_live_bond_coupon * current_slot_12;
if company.current_cash > 0 {
let prime_baseline = runtime_world_prime_rate_baseline(state)?;
let raw_issue_39 = runtime_world_issue_opinion_term_sum_raw(
state,
RUNTIME_WORLD_ISSUE_PRIME_RATE,
company.linked_chairman_profile_id,
Some(company_id),
None,
)? as f64;
finance_pressure +=
company.current_cash as f64 * (prime_baseline + raw_issue_39 * 0.01 + 0.03);
}
let profitability_ratio = if finance_pressure < 0.0 {
weighted_recent_profit / (-finance_pressure)
} else {
10.0
};
let mut profitability_score = runtime_credit_rating_profitability_ladder(profitability_ratio);
if let Some(years_since_founding) = annual_finance_state.years_since_founding {
if years_since_founding < 5 {
let missing_years = (5 - years_since_founding) as f64;
profitability_score += (10.0 - profitability_score) * missing_years * 0.1;
}
}
if current_slot_31 > 1_000_000.0 {
profitability_score += current_slot_31 / 100_000.0 - 10.0;
}
let burden_ratio = if current_slot_30 > 0.0 {
(weighted_recent_profit - current_slot_12) / current_slot_30
} else {
1.0
};
let burden_score = runtime_credit_rating_burden_ladder(burden_ratio);
let mut rating =
(profitability_score * burden_score / 10.0 + profitability_score + burden_score) / 3.0;
rating *= runtime_world_credit_market_scale(state)?;
if let Some(years_since_last_bankruptcy) = annual_finance_state.years_since_last_bankruptcy {
if years_since_last_bankruptcy < 15 {
rating *= years_since_last_bankruptcy as f64 * 0.0666;
}
}
let raw_issue_38 = runtime_world_issue_opinion_term_sum_raw(
state,
RUNTIME_WORLD_ISSUE_CREDIT_MARKET,
company.linked_chairman_profile_id,
Some(company_id),
None,
)? as f64;
runtime_round_f64_to_i64((rating + raw_issue_38 + 0.5).clamp(0.0, 10.0))
}
pub(super) fn runtime_credit_rating_profitability_ladder(ratio: f64) -> f64 {
if !ratio.is_finite() || ratio <= 0.0 {
0.0
} else if ratio < 1.0 {
1.0 + ratio * 4.0
} else if ratio < 2.0 {
3.0 + ratio * 2.0
} else if ratio < 5.0 {
5.0 + ratio
} else {
10.0
}
}
pub(super) fn runtime_credit_rating_burden_ladder(ratio: f64) -> f64 {
if !ratio.is_finite() || ratio > 1.0 {
0.0
} else if ratio > 0.75 {
16.0 - ratio * 16.0
} else if ratio > 0.5 {
13.0 - ratio * 12.0
} else if ratio > 0.25 {
11.0 - ratio * 8.0
} else if ratio > 0.1 {
10.0 - ratio * 4.0
} else {
10.0
}
}
pub fn runtime_world_credit_market_scale(state: &RuntimeState) -> Option<f64> {
const ISSUE_38_SCALE_TABLE: [f64; 8] = [0.8, 0.9, 1.0, 1.1, 1.2, 0.9, 0.95, 1.0];
let index = state.world_restore.issue_38_value? as usize;
ISSUE_38_SCALE_TABLE.get(index).copied()
}