From 7d02222da72203594416814d9c802cb64b5dc642 Mon Sep 17 00:00:00 2001 From: Immanuel Edunsin Date: Wed, 23 Jul 2025 14:44:57 +0100 Subject: [PATCH] Add files via upload --- analyze_results.py | 11 ++ app.py | 220 +++++++++++++++++++++++++++++++++ backtest.py | 144 ++++++++++++++++++++++ cip.py | 27 ++++ eur_usd_pricing.json | 74 +++++++++++ list_accounts.py | 17 +++ oanda_test.py | 92 ++++++++++++++ optimization_results.csv | 226 ++++++++++++++++++++++++++++++++++ optimization_results_real.csv | 37 ++++++ optimize.py | 114 +++++++++++++++++ pyvenv.cfg | 3 + requirements.txt | 6 + risk.py | 32 +++++ simulate_forward.py | 33 +++++ 14 files changed, 1036 insertions(+) create mode 100644 analyze_results.py create mode 100644 app.py create mode 100644 backtest.py create mode 100644 cip.py create mode 100644 eur_usd_pricing.json create mode 100644 list_accounts.py create mode 100644 oanda_test.py create mode 100644 optimization_results.csv create mode 100644 optimization_results_real.csv create mode 100644 optimize.py create mode 100644 pyvenv.cfg create mode 100644 requirements.txt create mode 100644 risk.py create mode 100644 simulate_forward.py diff --git a/analyze_results.py b/analyze_results.py new file mode 100644 index 0000000..9a3d7e2 --- /dev/null +++ b/analyze_results.py @@ -0,0 +1,11 @@ +import pandas as pd + +# 1) Load the CSV +df = pd.read_csv("optimization_results.csv") + +# 2) Sort by total_pnl descending +top = df.sort_values("total_pnl", ascending=False).head(10) + +# 3) Print to console +print("Top 10 parameter sets by Total PnL:\n", top.to_string(index=False)) + diff --git a/app.py b/app.py new file mode 100644 index 0000000..fcd4d80 --- /dev/null +++ b/app.py @@ -0,0 +1,220 @@ +import os +import requests +import random +import streamlit as st +import numpy as np +import pandas as pd +from dateutil import parser +from oandapyV20 import API +from oandapyV20.endpoints.pricing import PricingInfo +from cip import theoretical_forward, deviation_bps +from streamlit_autorefresh import st_autorefresh + +# Auto-refresh every 5 seconds +st_autorefresh(interval=5_000, key="refresh") + +# Page configuration +st.set_page_config(page_title="FX Arbitrage Dashboard", layout="wide") + +# --- Credentials via Streamlit secrets & Endpoints --- +# Create a file at ~/.streamlit/secrets.toml (or ./fx_arbitrage/.streamlit/secrets.toml) with: +# +# [oanda] +# token = "" +# account_id = "" +# +# [slack] +# webhook = "" +# +# Streamlit will auto-load this file into st.secrets +OANDA_TOKEN = st.secrets["oanda"]["token"] +OANDA_ACCOUNT_ID = st.secrets["oanda"]["account_id"] +SLACK_WEBHOOK = st.secrets.get("slack", {}).get("webhook", "") +PRACTICE_SWAP_API = "https://api-fxpractice.oanda.com" + +# Initialize OANDA client for spot data +client = API(access_token=OANDA_TOKEN, environment="practice") + +# --- Sidebar controls --- +st.sidebar.header("Settings") +override_provider = st.sidebar.selectbox( + "Forward-Rate Provider", ["Manual", "Swap-Points"] +) +manual_bps = st.sidebar.slider( + "Manual forward offset (bps)", -10.0, 10.0, 0.0, step=0.1 +) +threshold_bps = st.sidebar.number_input( + "Deviation threshold (bps)", 0.5, 10.0, 1.0, step=0.5 +) +stop_loss_bps = st.sidebar.number_input( + "Stop-loss threshold (bps)", 0.0, 20.0, 2.0, step=0.5 +) +spread_bps = st.sidebar.number_input( + "Spread cost per trade (bps)", 0.0, 5.0, 0.1, step=0.1 +) +tenor_days = st.sidebar.number_input( + "Tenor days", 1, 90, 30 +) +pairs = st.sidebar.multiselect( + "Currency pairs", ["EUR_USD", "GBP_USD", "USD_JPY"], default=["EUR_USD", "GBP_USD", "USD_JPY"] +) + +# Initialize histories +if 'history' not in st.session_state: + st.session_state.history = {pair: [] for pair in pairs} +if 'fwd_history' not in st.session_state: + st.session_state.fwd_history = {pair: [] for pair in pairs} + +# --- Utility functions --- +def fetch_spot(pair): + pricing = client.request( + PricingInfo(accountID=OANDA_ACCOUNT_ID, params={"instruments": pair}) + ) + bid = float(pricing["prices"][0]["bids"][0]["price"]) + ask = float(pricing["prices"][0]["asks"][0]["price"]) + return (bid + ask) / 2 + + +def fetch_swap_point(pair): + """Fetch the daily swap-point for the given tenor.""" + url = f"{PRACTICE_SWAP_API}/v3/accounts/{OANDA_ACCOUNT_ID}/instruments/{pair}/swap_rates" + headers = {"Authorization": f"Bearer {OANDA_TOKEN}"} + resp = requests.get(url, headers=headers) + if resp.status_code != 200: + return 0.0 + for r in resp.json().get("swapRates", []): + if r.get('tenor').endswith('D') and int(r.get('tenor')[:-1]) == tenor_days: + lr = float(r.get("longRate", 0)) + sr = float(r.get("shortRate", 0)) + return lr - sr + return 0.0 + + +def simulate_pnl(spot0, obs_fwd, days, sims=500): + pnls = [] + for _ in range(sims): + path = spot0 + daily = [] + for _ in range(days): + shock = random.uniform(-0.005, 0.005) + path *= (1 + shock) + daily.append(1e6 * (path - obs_fwd)) + pnls.append(daily) + return np.array(pnls) + +# --- Data collection & metrics --- +data_rows = [] +for pair in pairs: + spot_mid = fetch_spot(pair) + + # Determine observed forward + if override_provider == "Manual": + theo_fwd = theoretical_forward(spot_mid, 0.025, 0.005, tenor_days) + obs_fwd = theo_fwd * (1 + manual_bps / 10_000) + else: + swap_pts = fetch_swap_point(pair) + obs_fwd = spot_mid + swap_pts * tenor_days / 360 + theo_fwd = theoretical_forward(spot_mid, 0.025, 0.005, tenor_days) + + dev_bps = deviation_bps(obs_fwd, theo_fwd) + + # update histories + hist = st.session_state.history[pair] + hist.append(dev_bps) + if len(hist) > 50: + hist.pop(0) + st.session_state.history[pair] = hist + + fh = st.session_state.fwd_history[pair] + fh.append(obs_fwd) + if len(fh) > 50: + fh.pop(0) + st.session_state.fwd_history[pair] = fh + + # signal + if dev_bps > threshold_bps: + sig = "Rich → Sell forward" + elif dev_bps < -threshold_bps: + sig = "Cheap → Buy forward" + else: + sig = "No arbitrage" + + # PnL calculation + cost = spread_bps / 10_000 * 1_000_000 + raw_pnl = (obs_fwd - theo_fwd) * 1_000_000 + pnl = raw_pnl - cost + stop_amt = stop_loss_bps / 10_000 * 1_000_000 + if pnl < -stop_amt: + pnl = -stop_amt + + data_rows.append({ + "Pair": pair, + "Spot Mid": f"{spot_mid:.6f}", + "Observed Forward": f"{obs_fwd:.6f}", + "Theoretical Fwd": f"{theo_fwd:.6f}", + "Deviation (bps)": f"{dev_bps:+.2f}", + "Signal": sig, + "PnL ($)": f"{pnl:,.0f}" + }) + + if SLACK_WEBHOOK and sig != "No arbitrage": + requests.post(SLACK_WEBHOOK, json={"text": f"Arb alert: {pair} {dev_bps:+.2f}bps → {sig}"}) + +# --- Summary Metrics --- +col1, col2, col3, col4 = st.columns(4) +# parse PnL values from data_rows +pnls = [float(r["PnL ($)"].replace("$","").replace(",","") ) for r in data_rows] +total_pnl = sum(pnls) +win_rate = np.mean([1 if v>0 else 0 for v in pnls]) * 100 +max_dd = min(pnls) +current_dev= data_rows[0]["Deviation (bps)"] +col1.metric("Total PnL", f"${total_pnl:,.0f}") +col2.metric("Win Rate", f"{win_rate:.1f}%") +col3.metric("Max Drawdown", f"${max_dd:,.0f}") +col4.metric("Current Dev", f"{current_dev} bps") + +# --- Display dashboard --- +st.title("FX Arbitrage Dashboard — Live") +st.dataframe(pd.DataFrame(data_rows), use_container_width=True) +st.markdown("**Auto-refreshes every 5s**") + +# Deviation & Forward History +st.subheader("Deviation History (bps)") +for pair in pairs: + st.line_chart(pd.DataFrame({pair: st.session_state.history[pair]})) + +st.subheader("Observed Forward History") +for pair in pairs: + st.line_chart(pd.DataFrame({pair: st.session_state.fwd_history[pair]})) + +# PnL Distribution +st.subheader(f"PnL Distribution at Day {tenor_days}") +for pair in pairs: + spot_mid = fetch_spot(pair) + if override_provider == "Manual": + theo_fwd = theoretical_forward(spot_mid, 0.025, 0.005, tenor_days) + obs_fwd = theo_fwd * (1 + manual_bps / 10_000) + else: + swap_pts = fetch_swap_point(pair) + obs_fwd = spot_mid + swap_pts * tenor_days / 360 + sims = simulate_pnl(spot_mid, obs_fwd, tenor_days, sims=1000) + st.write(f"{pair} PnL Histogram") + st.bar_chart(pd.Series(sims[:, -1], name=pair)) + +# --- Equity Curve --- +st.subheader("Equity Curve (last 50 bars)") +for pair in pairs: + # compute per-bar PnL from history and forward history + pnl_series = [] + for obs, dev in zip(st.session_state.fwd_history[pair], st.session_state.history[pair]): + spot_val = fetch_spot(pair) + theo_val = theoretical_forward(spot_val, 0.025, 0.005, tenor_days) + raw = (obs - theo_val) * 1_000_000 + cost = spread_bps/10_000 * 1_000_000 + pnl_val = raw - cost + stop_amt = stop_loss_bps/10_000 * 1_000_000 + pnl_series.append(max(pnl_val, -stop_amt)) + equity = np.cumsum(pnl_series) + st.line_chart(pd.DataFrame({pair: equity})) + + diff --git a/backtest.py b/backtest.py new file mode 100644 index 0000000..4be2c3d --- /dev/null +++ b/backtest.py @@ -0,0 +1,144 @@ +import os +import requests +import pandas as pd +import matplotlib.pyplot as plt +from dateutil import parser +from oandapyV20 import API +from oandapyV20.endpoints.instruments import InstrumentsCandles +from cip import theoretical_forward, deviation_bps + +# === Configuration === +OANDA_TOKEN = os.getenv("OANDA_TOKEN") +OANDA_ACCOUNT_ID = os.getenv("OANDA_ACCOUNT_ID") +BASE_URL = "https://api-fxtrade.oanda.com" # production for swap rates + +if not OANDA_TOKEN or not OANDA_ACCOUNT_ID: + raise RuntimeError("Please set OANDA_TOKEN and OANDA_ACCOUNT_ID environment variables") + +# Initialize OANDA API client (practice for spot data) +api = API(access_token=OANDA_TOKEN, environment="practice") + +# === Data Fetching === + +def fetch_spot_history(pair: str, days: int = 365) -> pd.Series: + """ + Fetch daily historical spot mid-prices for the FX pair. + Returns a pandas Series indexed by date. + """ + req = InstrumentsCandles( + instrument=pair, + params={"granularity": "D", "count": days, "price": "M"} + ) + data = api.request(req)["candles"] + records = [] + for c in data: + dt = parser.isoparse(c["time"]) # full timestamp + o = float(c["mid"]["o"]) + c_ = float(c["mid"]["c"]) + records.append((dt.date(), (o + c_) / 2)) + series = pd.Series({d: s for d, s in records}).sort_index() + return series + + +def fetch_swap_history(pair: str, days: int = 365) -> pd.Series: + """ + Fetch daily historical swap-rates (forward-points) for the FX pair. + Returns a pandas Series of daily forward-points (decimal) indexed by date. + Falls back to zeros if endpoint unavailable (e.g., practice account). + """ + url = f"{BASE_URL}/v3/accounts/{OANDA_ACCOUNT_ID}/instruments/{pair}/swap_rates" + headers = {"Authorization": f"Bearer {OANDA_TOKEN}"} + params = {"count": days, "granularity": "D"} + try: + resp = requests.get(url, headers=headers, params=params) + resp.raise_for_status() + data = resp.json().get("swapRates", []) + records = [] + for r in data: + dt = parser.isoparse(r["time"]).date() + long_rate = float(r.get("longRate", 0)) + short_rate = float(r.get("shortRate", 0)) + records.append((dt, long_rate - short_rate)) + series = pd.Series({d: p for d, p in records}).sort_index() + except Exception: + # Practice environment may not support swap_rates; fallback to zeros + print("Warning: swap_rates endpoint unavailable, falling back to zeros.") + # Build zero series over requested date range + df_spot = fetch_spot_history(pair, days=days) + series = pd.Series(0.0, index=df_spot.index) + return series + +# === Backtest === + +def backtest( + pair: str, + tenor_days: int = 30, + r_dom: float = 0.025, + r_for: float = 0.005, + notional: float = 1_000_000, + spread_bps: float = 0.5, + stop_loss_bps: float = 5.0, + history_days: int = 365 +) -> None: + """ + Back-test FX CIP arbitrage using real swap-points. + """ + # Fetch data + spot = fetch_spot_history(pair, days=history_days) + swap_pts = fetch_swap_history(pair, days=history_days) + + # Build DataFrame + df = pd.DataFrame({"spot": spot}) + # theoretical forward + df["theo_fwd"] = df["spot"].apply(lambda s: theoretical_forward(s, r_dom, r_for, tenor_days)) + # observed forward = spot + tenor * swap_pts/360 + df["swap_pts"] = swap_pts.reindex(df.index).fillna(method="ffill") + df["obs_fwd"] = df["spot"] + df["swap_pts"] * tenor_days / 360 + + # deviation and signal + df["dev_bps"] = (df["obs_fwd"] - df["theo_fwd"]) / df["theo_fwd"] * 10_000 + df["signal"] = 0 + df.loc[df["dev_bps"] > 0, "signal"] = -1 # sell forward if rich + df.loc[df["dev_bps"] < 0, "signal"] = +1 # buy forward if cheap + + # PnL with spread cost & stop-loss + cost = spread_bps / 10_000 * notional + df["exit_spot"] = df["spot"].shift(-tenor_days) + df["raw_pnl"] = df["signal"] * (df["exit_spot"] - df["obs_fwd"]) * notional + df["pnl"] = df["raw_pnl"] - df["signal"].abs() * cost + stop_amt = stop_loss_bps / 10_000 * notional + df.loc[df["pnl"] < -stop_amt, "pnl"] = -stop_amt + + # drop incomplete + trades = df.dropna(subset=["pnl"]) + + # metrics + total_pnl = trades["pnl"].sum() + num_trades = (trades["signal"] != 0).sum() + win_rate = trades["pnl"].gt(0).mean() * 100 if num_trades else 0 + avg_pnl = trades["pnl"].mean() if num_trades else 0 + equity = trades["pnl"].cumsum() + max_dd = (equity.cummax() - equity).max() if not equity.empty else 0 + + # output + print(f"=== Backtest Results for {pair} ({tenor_days}d tenor) ===") + print(f"Total PnL : ${total_pnl:,.0f}") + print(f"Number of trades : {num_trades}") + print(f"Win rate : {win_rate:.1f}%") + print(f"Average PnL/trade : ${avg_pnl:,.0f}") + print(f"Max Drawdown : ${max_dd:,.0f}") + + # plot equity + plt.figure(figsize=(10, 4)) + plt.plot(equity.index, equity.values) + plt.title(f"Equity Curve ({pair}, {tenor_days}d)") + plt.xlabel("Date") + plt.ylabel("Cumulative PnL ($)") + plt.grid(True) + plt.tight_layout() + plt.show() + +# === Main === +if __name__ == "__main__": + backtest("EUR_USD") + diff --git a/cip.py b/cip.py new file mode 100644 index 0000000..67bd8b5 --- /dev/null +++ b/cip.py @@ -0,0 +1,27 @@ +def theoretical_forward(spot: float, r_dom: float, r_for: float, tenor_days: int) -> float: + """ + Calculate the theoretical forward rate: + F = S * (1 + r_dom * (tenor_days/360)) / (1 + r_for * (tenor_days/360)) + """ + return spot * (1 + r_dom * tenor_days / 360) / (1 + r_for * tenor_days / 360) + +def deviation_bps(obs_fwd: float, theo_fwd: float) -> float: + """ + Compute the deviation between observed and theoretical forward, + expressed in basis points. + """ + return (obs_fwd - theo_fwd) / theo_fwd * 10_000 + +if __name__ == "__main__": + # Example inputs (replace these with your real data) + spot_rate = 1.16910 # from PricingInfo closeout or mid + observed_fwd = 1.16930 # placeholder forward outright + r_domestic = 0.025 # e.g., 2.5% annual domestic interest + r_foreign = 0.005 # e.g., 0.5% annual foreign interest + tenor_in_days = 30 # for 1M tenor, approx 30 days + + theo = theoretical_forward(spot_rate, r_domestic, r_foreign, tenor_in_days) + dev = deviation_bps(observed_fwd, theo) + + print(f"Theoretical 1M Forward: {theo:.6f}") + print(f"Deviation: {dev:.2f} bps") diff --git a/eur_usd_pricing.json b/eur_usd_pricing.json new file mode 100644 index 0000000..accde0c --- /dev/null +++ b/eur_usd_pricing.json @@ -0,0 +1,74 @@ +{ + "time": "2025-07-22T10:40:15.470477032Z", + "prices": [ + { + "type": "PRICE", + "time": "2025-07-22T10:40:10.297022351Z", + "bids": [ + { + "price": "1.17025", + "liquidity": 500000 + }, + { + "price": "1.17024", + "liquidity": 500000 + }, + { + "price": "1.17023", + "liquidity": 2000000 + }, + { + "price": "1.17022", + "liquidity": 2000000 + }, + { + "price": "1.17021", + "liquidity": 5000000 + }, + { + "price": "1.17019", + "liquidity": 10000000 + }, + { + "price": "1.17016", + "liquidity": 10000000 + } + ], + "asks": [ + { + "price": "1.17032", + "liquidity": 500000 + }, + { + "price": "1.17034", + "liquidity": 2500000 + }, + { + "price": "1.17035", + "liquidity": 2000000 + }, + { + "price": "1.17036", + "liquidity": 5000000 + }, + { + "price": "1.17039", + "liquidity": 10000000 + }, + { + "price": "1.17042", + "liquidity": 10000000 + } + ], + "closeoutBid": "1.17016", + "closeoutAsk": "1.17042", + "status": "tradeable", + "tradeable": true, + "quoteHomeConversionFactors": { + "positiveUnits": "0.74144374", + "negativeUnits": "0.74155370" + }, + "instrument": "EUR_USD" + } + ] +} diff --git a/list_accounts.py b/list_accounts.py new file mode 100644 index 0000000..adeff74 --- /dev/null +++ b/list_accounts.py @@ -0,0 +1,17 @@ +import os, json +from oandapyV20 import API +from oandapyV20.endpoints.accounts import AccountList + +# 1. Read your practice token from env +token = os.getenv("OANDA_TOKEN") + +# 2. Initialize the client in practice mode +client = API(access_token=token, environment="practice") + +# 3. Create and send the AccountList request +req = AccountList() +resp = client.request(req) + +# 4. Pretty-print the JSON so you can see your account IDs +print(json.dumps(resp, indent=2)) + \ No newline at end of file diff --git a/oanda_test.py b/oanda_test.py new file mode 100644 index 0000000..d8beeaa --- /dev/null +++ b/oanda_test.py @@ -0,0 +1,92 @@ +import warnings +from urllib3.exceptions import NotOpenSSLWarning + +# Silence the LibreSSL/OpenSSL warning +warnings.filterwarnings("ignore", category=NotOpenSSLWarning) + +import os +import json +import requests +from oandapyV20 import API +from oandapyV20.endpoints.pricing import PricingInfo +from cip import theoretical_forward, deviation_bps + +# 1. Read credentials from environment variables +# Ensure OANDA_TOKEN and OANDA_ACCOUNT_ID are exported in the same shell +token = os.getenv("OANDA_TOKEN") +account_id = os.getenv("OANDA_ACCOUNT_ID") + +# Debug: verify credentials are loaded (remove after confirming) +print("DEBUG: token →", token) +print("DEBUG: account_id →", account_id) + +# 2. Initialize OANDA client (practice environment) +client = API(access_token=token, environment="practice") + +# 3. Fetch spot pricing for EUR/USD +pricing_req = PricingInfo(accountID=account_id, params={"instruments": "EUR_USD"}) +pricing_resp = client.request(pricing_req) +print("\nSPOT PRICING:") +print(json.dumps(pricing_resp, indent=2)) + +# 4. Compute spot mid price +bid = float(pricing_resp["prices"][0]["bids"][0]["price"]) +ask = float(pricing_resp["prices"][0]["asks"][0]["price"]) +spot_mid = (bid + ask) / 2 +print(f"Spot mid: {spot_mid:.6f}") + +# 5. Fetch all swap rates for EUR/USD via correct endpoint +swap_url = "https://api-fxpractice.oanda.com/v3/instruments/EUR_USD/swap_rates" +headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json" +} +swap_resp = requests.get(swap_url, headers=headers) +swap_data = swap_resp.json() +print("\nSWAP RATES RESPONSE:") +print(json.dumps(swap_data, indent=2)) + +# 6. Extract 1M swap-rate if available +days = 30 # tenor in days for 1M +swap_rates = swap_data.get("swapRates", []) +if swap_rates: + rate_1m = next((r for r in swap_rates if r.get("tenor") == "1M"), None) + if rate_1m: + print("\nObserved market 1M swap-rate object:") + print(json.dumps(rate_1m, indent=2)) + # Compute observed forward outright: spot_mid + swap points + fwd_pts = (rate_1m["longRate"] - rate_1m["shortRate"]) * days / 360 + obs_fwd = spot_mid + fwd_pts + print(f"Observed 1M forward (spot + swap points): {obs_fwd:.6f}") + else: + print("\n⚠️ 1M tenor not found in swapRates; falling back to theoretical CIP") + # placeholder interest rates + r_domestic = 0.025 # e.g., USD OIS + r_foreign = 0.005 # e.g., EUR OIS + obs_fwd = theoretical_forward(spot_mid, r_domestic, r_foreign, days) + print(f"Fallback observed forward: {obs_fwd:.6f}") +else: + print("\n⚠️ No swapRates data; using theoretical CIP as observed forward") + # placeholder interest rates + r_domestic = 0.025 + r_foreign = 0.005 + obs_fwd = theoretical_forward(spot_mid, r_domestic, r_foreign, days) + print(f"Fallback observed forward: {obs_fwd:.6f}") + +# 7. Compute theoretical forward and deviation +# placeholder interest rates (update with live data when available) +r_domestic = 0.025 +r_foreign = 0.005 +theo_fwd = theoretical_forward(spot_mid, r_domestic, r_foreign, days) +dev_bps = deviation_bps(obs_fwd, theo_fwd) +print(f"\nTheoretical 1M Forward: {theo_fwd:.6f}") +print(f"Deviation : {dev_bps:.2f} bps") + +# 8. Flag arbitrage signal if deviation exceeds threshold +threshold = 2.0 # bps +if abs(dev_bps) > threshold: + direction = "Sell forward / Buy spot" if dev_bps > 0 else "Buy forward / Sell spot" + print(f"⚠️ Arbitrage signal: {dev_bps:.2f} bps → {direction}") +else: + print("✅ No actionable arbitrage (deviation within threshold).") + diff --git a/optimization_results.csv b/optimization_results.csv new file mode 100644 index 0000000..2511093 --- /dev/null +++ b/optimization_results.csv @@ -0,0 +1,226 @@ +threshold_bps,offset_bps,stop_loss_bps,spread_bps,total_pnl,num_trades,win_rate,avg_pnl,max_drawdown +0.5,1.0,2.0,0.1,3035914.489215958,335,44.47761194029851,9062.431311092412,14800.0 +0.5,1.0,2.0,0.5,3029914.489215958,335,44.47761194029851,9044.520863331218,14800.0 +0.5,1.0,2.0,1.0,3022414.489215958,335,44.47761194029851,9022.132803629725,14800.0 +0.5,1.0,5.0,0.1,2980414.489215958,335,44.47761194029851,8896.759669301367,37000.0 +0.5,1.0,5.0,0.5,2974414.489215958,335,44.47761194029851,8878.849221540173,37000.0 +0.5,1.0,5.0,1.0,2966914.489215958,335,44.47761194029851,8856.46116183868,37000.0 +0.5,1.0,10.0,0.1,2888282.456922122,335,44.47761194029851,8621.738677379468,75079.10061016213 +0.5,1.0,10.0,0.5,2882242.456922122,335,44.47761194029851,8603.7088266332,75399.10061016213 +0.5,1.0,10.0,1.0,2874692.456922122,335,44.47761194029851,8581.171513200363,75799.10061016213 +0.5,2.0,2.0,0.1,3052218.607753026,335,44.776119402985074,9111.100321650823,14800.0 +0.5,2.0,2.0,0.5,3046218.607753026,335,44.47761194029851,9093.189873889629,14800.0 +0.5,2.0,2.0,1.0,3038718.607753026,335,44.47761194029851,9070.801814188137,14800.0 +0.5,2.0,5.0,0.1,2996718.607753026,335,44.776119402985074,8945.428679859779,37000.0 +0.5,2.0,5.0,0.5,2990718.607753026,335,44.47761194029851,8927.518232098584,37000.0 +0.5,2.0,5.0,1.0,2983218.607753026,335,44.47761194029851,8905.130172397092,37000.0 +0.5,2.0,10.0,0.1,2904773.3181278696,335,44.776119402985074,8670.96512873991,74165.65436484851 +0.5,2.0,10.0,0.5,2898693.3181278696,335,44.47761194029851,8652.815875008566,74485.65436484851 +0.5,2.0,10.0,1.0,2891110.4573677694,335,44.47761194029851,8630.180469754536,74885.65436484851 +0.5,3.0,2.0,0.1,3068522.72629009,335,44.776119402985074,9159.769332209224,14800.0 +0.5,3.0,2.0,0.5,3062522.72629009,335,44.776119402985074,9141.85888444803,14800.0 +0.5,3.0,2.0,1.0,3055022.72629009,335,44.776119402985074,9119.470824746537,14800.0 +0.5,3.0,5.0,0.1,3013118.457813414,335,44.776119402985074,8994.383456159445,37000.0 +0.5,3.0,5.0,0.5,3007078.457813414,335,44.776119402985074,8976.353605413176,37000.0 +0.5,3.0,5.0,1.0,2999528.457813414,335,44.776119402985074,8953.81629198034,37000.0 +0.5,3.0,10.0,0.1,2921299.887645775,335,44.776119402985074,8720.29817207694,74000.0 +0.5,3.0,10.0,0.5,2915219.887645775,335,44.776119402985074,8702.148918345598,74000.0 +0.5,3.0,10.0,1.0,2907619.887645775,335,44.776119402985074,8679.462351181417,74000.0 +0.5,4.0,2.0,0.1,3084826.8448271556,335,44.776119402985074,9208.43834276763,14800.0 +0.5,4.0,2.0,0.5,3078826.8448271556,335,44.776119402985074,9190.527895006435,14800.0 +0.5,4.0,2.0,1.0,3071326.8448271556,335,44.776119402985074,9168.139835304943,14800.0 +0.5,4.0,5.0,0.1,3029536.458259059,335,44.776119402985074,9043.392412713609,37000.0 +0.5,4.0,5.0,0.5,3023496.458259059,335,44.776119402985074,9025.36256196734,37000.0 +0.5,4.0,5.0,1.0,3015946.458259059,335,44.776119402985074,9002.825248534506,37000.0 +0.5,4.0,10.0,0.1,2937917.0644606417,335,44.776119402985074,8769.90168495714,74000.0 +0.5,4.0,10.0,0.5,2931797.0644606417,335,44.776119402985074,8751.633028240722,74000.0 +0.5,4.0,10.0,1.0,2924147.0644606417,335,44.776119402985074,8728.797207345198,74000.0 +0.5,5.0,2.0,0.1,3101154.4587047044,335,44.776119402985074,9257.177488670759,14800.0 +0.5,5.0,2.0,0.5,3095130.963364221,335,44.776119402985074,9239.196905564839,14800.0 +0.5,5.0,2.0,1.0,3087630.963364221,335,44.776119402985074,9216.808845863347,14800.0 +0.5,5.0,5.0,0.1,3045954.4587047044,335,44.776119402985074,9092.401369267774,37000.0 +0.5,5.0,5.0,0.5,3039914.4587047044,335,44.776119402985074,9074.371518521506,37000.0 +0.5,5.0,5.0,1.0,3032364.4587047044,335,44.776119402985074,9051.83420508867,37000.0 +0.5,5.0,10.0,0.1,2954551.9901967905,335,44.776119402985074,8819.558179691912,74000.0 +0.5,5.0,10.0,0.5,2948431.9901967905,335,44.776119402985074,8801.289522975494,74000.0 +0.5,5.0,10.0,1.0,2940781.9901967905,335,44.776119402985074,8778.453702079973,74000.0 +1.0,1.0,2.0,0.1,1233700.1759173332,160,19.1044776119403,3682.687092290547,7200.0 +1.0,1.0,2.0,0.5,1231100.1759173332,160,19.1044776119403,3674.925898260696,7200.0 +1.0,1.0,2.0,1.0,1227850.1759173332,160,19.1044776119403,3665.224405723383,7200.0 +1.0,1.0,5.0,0.1,1205200.1759173332,160,19.1044776119403,3597.612465424875,18000.0 +1.0,1.0,5.0,0.5,1202600.1759173332,160,19.1044776119403,3589.8512713950245,18000.0 +1.0,1.0,5.0,1.0,1199350.1759173332,160,19.1044776119403,3580.149778857711,18000.0 +1.0,1.0,10.0,0.1,1157700.1759173332,160,19.1044776119403,3455.821420648756,42775.720814244356 +1.0,1.0,10.0,0.5,1155100.1759173332,160,19.1044776119403,3448.0602266189053,42895.720814244356 +1.0,1.0,10.0,1.0,1151850.1759173332,160,19.1044776119403,3438.3587340815916,43045.720814244356 +1.0,2.0,2.0,0.1,3052218.607753026,335,44.776119402985074,9111.100321650823,14800.0 +1.0,2.0,2.0,0.5,3046218.607753026,335,44.47761194029851,9093.189873889629,14800.0 +1.0,2.0,2.0,1.0,3038718.607753026,335,44.47761194029851,9070.801814188137,14800.0 +1.0,2.0,5.0,0.1,2996718.607753026,335,44.776119402985074,8945.428679859779,37000.0 +1.0,2.0,5.0,0.5,2990718.607753026,335,44.47761194029851,8927.518232098584,37000.0 +1.0,2.0,5.0,1.0,2983218.607753026,335,44.47761194029851,8905.130172397092,37000.0 +1.0,2.0,10.0,0.1,2904773.3181278696,335,44.776119402985074,8670.96512873991,74165.65436484851 +1.0,2.0,10.0,0.5,2898693.3181278696,335,44.47761194029851,8652.815875008566,74485.65436484851 +1.0,2.0,10.0,1.0,2891110.4573677694,335,44.47761194029851,8630.180469754536,74885.65436484851 +1.0,3.0,2.0,0.1,3068522.72629009,335,44.776119402985074,9159.769332209224,14800.0 +1.0,3.0,2.0,0.5,3062522.72629009,335,44.776119402985074,9141.85888444803,14800.0 +1.0,3.0,2.0,1.0,3055022.72629009,335,44.776119402985074,9119.470824746537,14800.0 +1.0,3.0,5.0,0.1,3013118.457813414,335,44.776119402985074,8994.383456159445,37000.0 +1.0,3.0,5.0,0.5,3007078.457813414,335,44.776119402985074,8976.353605413176,37000.0 +1.0,3.0,5.0,1.0,2999528.457813414,335,44.776119402985074,8953.81629198034,37000.0 +1.0,3.0,10.0,0.1,2921299.887645775,335,44.776119402985074,8720.29817207694,74000.0 +1.0,3.0,10.0,0.5,2915219.887645775,335,44.776119402985074,8702.148918345598,74000.0 +1.0,3.0,10.0,1.0,2907619.887645775,335,44.776119402985074,8679.462351181417,74000.0 +1.0,4.0,2.0,0.1,3084826.8448271556,335,44.776119402985074,9208.43834276763,14800.0 +1.0,4.0,2.0,0.5,3078826.8448271556,335,44.776119402985074,9190.527895006435,14800.0 +1.0,4.0,2.0,1.0,3071326.8448271556,335,44.776119402985074,9168.139835304943,14800.0 +1.0,4.0,5.0,0.1,3029536.458259059,335,44.776119402985074,9043.392412713609,37000.0 +1.0,4.0,5.0,0.5,3023496.458259059,335,44.776119402985074,9025.36256196734,37000.0 +1.0,4.0,5.0,1.0,3015946.458259059,335,44.776119402985074,9002.825248534506,37000.0 +1.0,4.0,10.0,0.1,2937917.0644606417,335,44.776119402985074,8769.90168495714,74000.0 +1.0,4.0,10.0,0.5,2931797.0644606417,335,44.776119402985074,8751.633028240722,74000.0 +1.0,4.0,10.0,1.0,2924147.0644606417,335,44.776119402985074,8728.797207345198,74000.0 +1.0,5.0,2.0,0.1,3101154.4587047044,335,44.776119402985074,9257.177488670759,14800.0 +1.0,5.0,2.0,0.5,3095130.963364221,335,44.776119402985074,9239.196905564839,14800.0 +1.0,5.0,2.0,1.0,3087630.963364221,335,44.776119402985074,9216.808845863347,14800.0 +1.0,5.0,5.0,0.1,3045954.4587047044,335,44.776119402985074,9092.401369267774,37000.0 +1.0,5.0,5.0,0.5,3039914.4587047044,335,44.776119402985074,9074.371518521506,37000.0 +1.0,5.0,5.0,1.0,3032364.4587047044,335,44.776119402985074,9051.83420508867,37000.0 +1.0,5.0,10.0,0.1,2954551.9901967905,335,44.776119402985074,8819.558179691912,74000.0 +1.0,5.0,10.0,0.5,2948431.9901967905,335,44.776119402985074,8801.289522975494,74000.0 +1.0,5.0,10.0,1.0,2940781.9901967905,335,44.776119402985074,8778.453702079973,74000.0 +2.0,1.0,2.0,0.1,0.0,0,0.0,0.0,0.0 +2.0,1.0,2.0,0.5,0.0,0,0.0,0.0,0.0 +2.0,1.0,2.0,1.0,0.0,0,0.0,0.0,0.0 +2.0,1.0,5.0,0.1,0.0,0,0.0,0.0,0.0 +2.0,1.0,5.0,0.5,0.0,0,0.0,0.0,0.0 +2.0,1.0,5.0,1.0,0.0,0,0.0,0.0,0.0 +2.0,1.0,10.0,0.1,0.0,0,0.0,0.0,0.0 +2.0,1.0,10.0,0.5,0.0,0,0.0,0.0,0.0 +2.0,1.0,10.0,1.0,0.0,0,0.0,0.0,0.0 +2.0,2.0,2.0,0.1,1234607.5833340343,130,18.507462686567163,3685.395771146371,5800.0 +2.0,2.0,2.0,0.5,1232127.5833340343,130,18.507462686567163,3677.9927860717444,5800.0 +2.0,2.0,2.0,1.0,1229027.5833340343,130,18.507462686567163,3668.739054728461,5800.0 +2.0,2.0,5.0,0.1,1214207.5833340343,130,18.507462686567163,3624.5002487583115,14500.0 +2.0,2.0,5.0,0.5,1211727.5833340343,130,18.507462686567163,3617.0972636836846,14500.0 +2.0,2.0,5.0,1.0,1208627.5833340343,130,18.507462686567163,3607.843532340401,14500.0 +2.0,2.0,10.0,0.1,1180207.5833340343,130,18.507462686567163,3523.0077114448786,29000.0 +2.0,2.0,10.0,0.5,1177727.5833340343,130,18.507462686567163,3515.6047263702517,29000.0 +2.0,2.0,10.0,1.0,1174627.5833340343,130,18.507462686567163,3506.350995026968,29000.0 +2.0,3.0,2.0,0.1,3068522.72629009,335,44.776119402985074,9159.769332209224,14800.0 +2.0,3.0,2.0,0.5,3062522.72629009,335,44.776119402985074,9141.85888444803,14800.0 +2.0,3.0,2.0,1.0,3055022.72629009,335,44.776119402985074,9119.470824746537,14800.0 +2.0,3.0,5.0,0.1,3013118.457813414,335,44.776119402985074,8994.383456159445,37000.0 +2.0,3.0,5.0,0.5,3007078.457813414,335,44.776119402985074,8976.353605413176,37000.0 +2.0,3.0,5.0,1.0,2999528.457813414,335,44.776119402985074,8953.81629198034,37000.0 +2.0,3.0,10.0,0.1,2921299.887645775,335,44.776119402985074,8720.29817207694,74000.0 +2.0,3.0,10.0,0.5,2915219.887645775,335,44.776119402985074,8702.148918345598,74000.0 +2.0,3.0,10.0,1.0,2907619.887645775,335,44.776119402985074,8679.462351181417,74000.0 +2.0,4.0,2.0,0.1,3084826.8448271556,335,44.776119402985074,9208.43834276763,14800.0 +2.0,4.0,2.0,0.5,3078826.8448271556,335,44.776119402985074,9190.527895006435,14800.0 +2.0,4.0,2.0,1.0,3071326.8448271556,335,44.776119402985074,9168.139835304943,14800.0 +2.0,4.0,5.0,0.1,3029536.458259059,335,44.776119402985074,9043.392412713609,37000.0 +2.0,4.0,5.0,0.5,3023496.458259059,335,44.776119402985074,9025.36256196734,37000.0 +2.0,4.0,5.0,1.0,3015946.458259059,335,44.776119402985074,9002.825248534506,37000.0 +2.0,4.0,10.0,0.1,2937917.0644606417,335,44.776119402985074,8769.90168495714,74000.0 +2.0,4.0,10.0,0.5,2931797.0644606417,335,44.776119402985074,8751.633028240722,74000.0 +2.0,4.0,10.0,1.0,2924147.0644606417,335,44.776119402985074,8728.797207345198,74000.0 +2.0,5.0,2.0,0.1,3101154.4587047044,335,44.776119402985074,9257.177488670759,14800.0 +2.0,5.0,2.0,0.5,3095130.963364221,335,44.776119402985074,9239.196905564839,14800.0 +2.0,5.0,2.0,1.0,3087630.963364221,335,44.776119402985074,9216.808845863347,14800.0 +2.0,5.0,5.0,0.1,3045954.4587047044,335,44.776119402985074,9092.401369267774,37000.0 +2.0,5.0,5.0,0.5,3039914.4587047044,335,44.776119402985074,9074.371518521506,37000.0 +2.0,5.0,5.0,1.0,3032364.4587047044,335,44.776119402985074,9051.83420508867,37000.0 +2.0,5.0,10.0,0.1,2954551.9901967905,335,44.776119402985074,8819.558179691912,74000.0 +2.0,5.0,10.0,0.5,2948431.9901967905,335,44.776119402985074,8801.289522975494,74000.0 +2.0,5.0,10.0,1.0,2940781.9901967905,335,44.776119402985074,8778.453702079973,74000.0 +3.0,1.0,2.0,0.1,0.0,0,0.0,0.0,0.0 +3.0,1.0,2.0,0.5,0.0,0,0.0,0.0,0.0 +3.0,1.0,2.0,1.0,0.0,0,0.0,0.0,0.0 +3.0,1.0,5.0,0.1,0.0,0,0.0,0.0,0.0 +3.0,1.0,5.0,0.5,0.0,0,0.0,0.0,0.0 +3.0,1.0,5.0,1.0,0.0,0,0.0,0.0,0.0 +3.0,1.0,10.0,0.1,0.0,0,0.0,0.0,0.0 +3.0,1.0,10.0,0.5,0.0,0,0.0,0.0,0.0 +3.0,1.0,10.0,1.0,0.0,0,0.0,0.0,0.0 +3.0,2.0,2.0,0.1,0.0,0,0.0,0.0,0.0 +3.0,2.0,2.0,0.5,0.0,0,0.0,0.0,0.0 +3.0,2.0,2.0,1.0,0.0,0,0.0,0.0,0.0 +3.0,2.0,5.0,0.1,0.0,0,0.0,0.0,0.0 +3.0,2.0,5.0,0.5,0.0,0,0.0,0.0,0.0 +3.0,2.0,5.0,1.0,0.0,0,0.0,0.0,0.0 +3.0,2.0,10.0,0.1,0.0,0,0.0,0.0,0.0 +3.0,2.0,10.0,0.5,0.0,0,0.0,0.0,0.0 +3.0,2.0,10.0,1.0,0.0,0,0.0,0.0,0.0 +3.0,3.0,2.0,0.1,1136323.1076457775,116,16.119402985074625,3392.0092765545596,5000.0 +3.0,3.0,2.0,0.5,1134163.1076457775,116,16.119402985074625,3385.56151536053,5000.0 +3.0,3.0,2.0,1.0,1131463.1076457775,116,16.119402985074625,3377.5018138679925,5000.0 +3.0,3.0,5.0,0.1,1117723.1076457775,116,16.119402985074625,3336.4868884948582,12500.0 +3.0,3.0,5.0,0.5,1115563.1076457775,116,16.119402985074625,3330.0391273008286,12500.0 +3.0,3.0,5.0,1.0,1112863.1076457775,116,16.119402985074625,3321.979425808291,12500.0 +3.0,3.0,10.0,0.1,1086723.1076457775,116,16.119402985074625,3243.9495750620226,25000.0 +3.0,3.0,10.0,0.5,1084563.1076457775,116,16.119402985074625,3237.5018138679925,25000.0 +3.0,3.0,10.0,1.0,1081863.1076457775,116,16.119402985074625,3229.4421123754555,25000.0 +3.0,4.0,2.0,0.1,3084826.8448271556,335,44.776119402985074,9208.43834276763,14800.0 +3.0,4.0,2.0,0.5,3078826.8448271556,335,44.776119402985074,9190.527895006435,14800.0 +3.0,4.0,2.0,1.0,3071326.8448271556,335,44.776119402985074,9168.139835304943,14800.0 +3.0,4.0,5.0,0.1,3029536.458259059,335,44.776119402985074,9043.392412713609,37000.0 +3.0,4.0,5.0,0.5,3023496.458259059,335,44.776119402985074,9025.36256196734,37000.0 +3.0,4.0,5.0,1.0,3015946.458259059,335,44.776119402985074,9002.825248534506,37000.0 +3.0,4.0,10.0,0.1,2937917.0644606417,335,44.776119402985074,8769.90168495714,74000.0 +3.0,4.0,10.0,0.5,2931797.0644606417,335,44.776119402985074,8751.633028240722,74000.0 +3.0,4.0,10.0,1.0,2924147.0644606417,335,44.776119402985074,8728.797207345198,74000.0 +3.0,5.0,2.0,0.1,3101154.4587047044,335,44.776119402985074,9257.177488670759,14800.0 +3.0,5.0,2.0,0.5,3095130.963364221,335,44.776119402985074,9239.196905564839,14800.0 +3.0,5.0,2.0,1.0,3087630.963364221,335,44.776119402985074,9216.808845863347,14800.0 +3.0,5.0,5.0,0.1,3045954.4587047044,335,44.776119402985074,9092.401369267774,37000.0 +3.0,5.0,5.0,0.5,3039914.4587047044,335,44.776119402985074,9074.371518521506,37000.0 +3.0,5.0,5.0,1.0,3032364.4587047044,335,44.776119402985074,9051.83420508867,37000.0 +3.0,5.0,10.0,0.1,2954551.9901967905,335,44.776119402985074,8819.558179691912,74000.0 +3.0,5.0,10.0,0.5,2948431.9901967905,335,44.776119402985074,8801.289522975494,74000.0 +3.0,5.0,10.0,1.0,2940781.9901967905,335,44.776119402985074,8778.453702079973,74000.0 +4.0,1.0,2.0,0.1,0.0,0,0.0,0.0,0.0 +4.0,1.0,2.0,0.5,0.0,0,0.0,0.0,0.0 +4.0,1.0,2.0,1.0,0.0,0,0.0,0.0,0.0 +4.0,1.0,5.0,0.1,0.0,0,0.0,0.0,0.0 +4.0,1.0,5.0,0.5,0.0,0,0.0,0.0,0.0 +4.0,1.0,5.0,1.0,0.0,0,0.0,0.0,0.0 +4.0,1.0,10.0,0.1,0.0,0,0.0,0.0,0.0 +4.0,1.0,10.0,0.5,0.0,0,0.0,0.0,0.0 +4.0,1.0,10.0,1.0,0.0,0,0.0,0.0,0.0 +4.0,2.0,2.0,0.1,0.0,0,0.0,0.0,0.0 +4.0,2.0,2.0,0.5,0.0,0,0.0,0.0,0.0 +4.0,2.0,2.0,1.0,0.0,0,0.0,0.0,0.0 +4.0,2.0,5.0,0.1,0.0,0,0.0,0.0,0.0 +4.0,2.0,5.0,0.5,0.0,0,0.0,0.0,0.0 +4.0,2.0,5.0,1.0,0.0,0,0.0,0.0,0.0 +4.0,2.0,10.0,0.1,0.0,0,0.0,0.0,0.0 +4.0,2.0,10.0,0.5,0.0,0,0.0,0.0,0.0 +4.0,2.0,10.0,1.0,0.0,0,0.0,0.0,0.0 +4.0,3.0,2.0,0.1,0.0,0,0.0,0.0,0.0 +4.0,3.0,2.0,0.5,0.0,0,0.0,0.0,0.0 +4.0,3.0,2.0,1.0,0.0,0,0.0,0.0,0.0 +4.0,3.0,5.0,0.1,0.0,0,0.0,0.0,0.0 +4.0,3.0,5.0,0.5,0.0,0,0.0,0.0,0.0 +4.0,3.0,5.0,1.0,0.0,0,0.0,0.0,0.0 +4.0,3.0,10.0,0.1,0.0,0,0.0,0.0,0.0 +4.0,3.0,10.0,0.5,0.0,0,0.0,0.0,0.0 +4.0,3.0,10.0,1.0,0.0,0,0.0,0.0,0.0 +4.0,4.0,2.0,0.1,744222.7130903826,90,11.343283582089553,2221.5603375832316,6948.339329446317 +4.0,4.0,2.0,0.5,742702.7130903827,90,11.343283582089553,2217.0230241503964,6988.339329446317 +4.0,4.0,2.0,1.0,740802.7130903827,90,11.343283582089553,2211.3513823593516,7038.339329446317 +4.0,4.0,5.0,0.1,728622.7130903827,90,11.343283582089553,2174.9931734041274,17748.339329446317 +4.0,4.0,5.0,0.5,727102.7130903827,90,11.343283582089553,2170.455859971292,17788.339329446317 +4.0,4.0,5.0,1.0,725202.7130903827,90,11.343283582089553,2164.784218180247,17838.339329446317 +4.0,4.0,10.0,0.1,702713.3203873425,90,11.343283582089553,2097.6517026487836,35748.33932944632 +4.0,4.0,10.0,0.5,701153.3203873425,90,11.343283582089553,2092.994986230873,35788.33932944632 +4.0,4.0,10.0,1.0,699203.3203873425,90,11.343283582089553,2087.174090708485,35838.33932944632 +4.0,5.0,2.0,0.1,3101154.4587047044,335,44.776119402985074,9257.177488670759,14800.0 +4.0,5.0,2.0,0.5,3095130.963364221,335,44.776119402985074,9239.196905564839,14800.0 +4.0,5.0,2.0,1.0,3087630.963364221,335,44.776119402985074,9216.808845863347,14800.0 +4.0,5.0,5.0,0.1,3045954.4587047044,335,44.776119402985074,9092.401369267774,37000.0 +4.0,5.0,5.0,0.5,3039914.4587047044,335,44.776119402985074,9074.371518521506,37000.0 +4.0,5.0,5.0,1.0,3032364.4587047044,335,44.776119402985074,9051.83420508867,37000.0 +4.0,5.0,10.0,0.1,2954551.9901967905,335,44.776119402985074,8819.558179691912,74000.0 +4.0,5.0,10.0,0.5,2948431.9901967905,335,44.776119402985074,8801.289522975494,74000.0 +4.0,5.0,10.0,1.0,2940781.9901967905,335,44.776119402985074,8778.453702079973,74000.0 diff --git a/optimization_results_real.csv b/optimization_results_real.csv new file mode 100644 index 0000000..1239252 --- /dev/null +++ b/optimization_results_real.csv @@ -0,0 +1,37 @@ +threshold_bps,stop_loss_bps,spread_bps,total_pnl,num_trades,win_rate,avg_pnl,max_drawdown +0.5,2.0,0.1,5153890.000000001,335,57.611940298507456,15384.74626865672,18295.0 +0.5,2.0,0.5,5146160.000000002,335,57.611940298507456,15361.67164179105,18335.0 +0.5,2.0,1.0,5136510.000000002,335,57.611940298507456,15332.865671641797,18385.0 +0.5,5.0,0.1,5111590.000000001,335,57.611940298507456,15258.477611940301,46495.0 +0.5,5.0,0.5,5103830.000000001,335,57.611940298507456,15235.313432835823,46535.0 +0.5,5.0,1.0,5094130.000000001,335,57.611940298507456,15206.358208955227,46585.0 +0.5,10.0,0.1,5042665.0,335,57.611940298507456,15052.731343283582,92680.00000000047 +0.5,10.0,0.5,5034665.0,335,57.611940298507456,15028.850746268658,92840.00000000047 +0.5,10.0,1.0,5024730.000000001,335,57.611940298507456,14999.194029850749,93025.00000000023 +1.0,2.0,0.1,5153890.000000001,335,57.611940298507456,15384.74626865672,18295.0 +1.0,2.0,0.5,5146160.000000002,335,57.611940298507456,15361.67164179105,18335.0 +1.0,2.0,1.0,5136510.000000002,335,57.611940298507456,15332.865671641797,18385.0 +1.0,5.0,0.1,5111590.000000001,335,57.611940298507456,15258.477611940301,46495.0 +1.0,5.0,0.5,5103830.000000001,335,57.611940298507456,15235.313432835823,46535.0 +1.0,5.0,1.0,5094130.000000001,335,57.611940298507456,15206.358208955227,46585.0 +1.0,10.0,0.1,5042665.0,335,57.611940298507456,15052.731343283582,92680.00000000047 +1.0,10.0,0.5,5034665.0,335,57.611940298507456,15028.850746268658,92840.00000000047 +1.0,10.0,1.0,5024730.000000001,335,57.611940298507456,14999.194029850749,93025.00000000023 +2.0,2.0,0.1,5153890.000000001,335,57.611940298507456,15384.74626865672,18295.0 +2.0,2.0,0.5,5146160.000000002,335,57.611940298507456,15361.67164179105,18335.0 +2.0,2.0,1.0,5136510.000000002,335,57.611940298507456,15332.865671641797,18385.0 +2.0,5.0,0.1,5111590.000000001,335,57.611940298507456,15258.477611940301,46495.0 +2.0,5.0,0.5,5103830.000000001,335,57.611940298507456,15235.313432835823,46535.0 +2.0,5.0,1.0,5094130.000000001,335,57.611940298507456,15206.358208955227,46585.0 +2.0,10.0,0.1,5042665.0,335,57.611940298507456,15052.731343283582,92680.00000000047 +2.0,10.0,0.5,5034665.0,335,57.611940298507456,15028.850746268658,92840.00000000047 +2.0,10.0,1.0,5024730.000000001,335,57.611940298507456,14999.194029850749,93025.00000000023 +3.0,2.0,0.1,5153890.000000001,335,57.611940298507456,15384.74626865672,18295.0 +3.0,2.0,0.5,5146160.000000002,335,57.611940298507456,15361.67164179105,18335.0 +3.0,2.0,1.0,5136510.000000002,335,57.611940298507456,15332.865671641797,18385.0 +3.0,5.0,0.1,5111590.000000001,335,57.611940298507456,15258.477611940301,46495.0 +3.0,5.0,0.5,5103830.000000001,335,57.611940298507456,15235.313432835823,46535.0 +3.0,5.0,1.0,5094130.000000001,335,57.611940298507456,15206.358208955227,46585.0 +3.0,10.0,0.1,5042665.0,335,57.611940298507456,15052.731343283582,92680.00000000047 +3.0,10.0,0.5,5034665.0,335,57.611940298507456,15028.850746268658,92840.00000000047 +3.0,10.0,1.0,5024730.000000001,335,57.611940298507456,14999.194029850749,93025.00000000023 diff --git a/optimize.py b/optimize.py new file mode 100644 index 0000000..58a826c --- /dev/null +++ b/optimize.py @@ -0,0 +1,114 @@ +import os +import itertools +import pandas as pd +import matplotlib.pyplot as plt +import requests +from dateutil import parser +from oandapyV20 import API +from oandapyV20.endpoints.instruments import InstrumentsCandles +from cip import theoretical_forward, deviation_bps + +# === Configuration === +OANDA_TOKEN = os.getenv("OANDA_TOKEN") +OANDA_ACCOUNT_ID = os.getenv("OANDA_ACCOUNT_ID") +BASE_URL_SWAP = "https://api-fxpractice.oanda.com" # practice swap endpoint + +if not OANDA_TOKEN or not OANDA_ACCOUNT_ID: + raise RuntimeError("Please set OANDA_TOKEN and OANDA_ACCOUNT_ID environment variables") + +# Initialize OANDA API client for spot +api = API(access_token=OANDA_TOKEN, environment="practice") + +# === Data fetching === +def fetch_spot_history(pair: str, days: int = 365) -> pd.Series: + req = InstrumentsCandles( + instrument=pair, + params={"granularity": "D", "count": days, "price": "M"} + ) + data = api.request(req)["candles"] + records = [(parser.isoparse(c["time"]).date(), (float(c["mid"]["o"]) + float(c["mid"]["c"]))/2) + for c in data] + return pd.Series({d: s for d, s in records}).sort_index() + + +def fetch_swap_history(pair: str, days: int = 365) -> pd.Series: + url = f"{BASE_URL_SWAP}/v3/accounts/{OANDA_ACCOUNT_ID}/instruments/{pair}/swap_rates" + headers = {"Authorization": f"Bearer {OANDA_TOKEN}"} + params = {"count": days, "granularity": "D"} + try: + resp = requests.get(url, headers=headers, params=params) + resp.raise_for_status() + data = resp.json().get("swapRates", []) + records = [] + for r in data: + dt = parser.isoparse(r["time"]).date() + lr = float(r.get("longRate", 0)) + sr = float(r.get("shortRate", 0)) + records.append((dt, lr - sr)) + return pd.Series({d: p for d, p in records}).sort_index() + except Exception: + # fallback zeros + spot = fetch_spot_history(pair, days) + return pd.Series(0.0, index=spot.index) + +# === Backtest using real forward === +def run_backtest( + spot: pd.Series, + swap_pts: pd.Series, + threshold_bps: float, + stop_loss_bps: float, + spread_bps: float, + tenor_days: int = 30, + r_dom: float = 0.025, + r_for: float = 0.005, + notional: float = 1_000_000 +) -> dict: + df = pd.DataFrame({"spot": spot}) + df["swap_pts"] = swap_pts.reindex(df.index).fillna(method="ffill") + df["theo_fwd"] = df["spot"].apply(lambda s: theoretical_forward(s, r_dom, r_for, tenor_days)) + df["obs_fwd"] = df["spot"] + df["swap_pts"] * tenor_days / 360 + df["dev_bps"] = (df["obs_fwd"] - df["theo_fwd"]) / df["theo_fwd"] * 10_000 + df["signal"] = 0 + df.loc[df["dev_bps"] > threshold_bps, "signal"] = -1 + df.loc[df["dev_bps"] < -threshold_bps, "signal"] = +1 + cost = spread_bps / 10_000 * notional + df["exit_spot"] = df["spot"].shift(-tenor_days) + df["raw_pnl"] = df["signal"] * (df["exit_spot"] - df["obs_fwd"]) * notional + df["pnl"] = df["raw_pnl"] - df["signal"].abs() * cost + stop_amt = stop_loss_bps / 10_000 * notional + df.loc[df["pnl"] < -stop_amt, "pnl"] = -stop_amt + trades = df.dropna(subset=["pnl"]) + total_pnl = trades["pnl"].sum() + num_trades = (trades["signal"] != 0).sum() + win_rate = trades["pnl"].gt(0).mean() * 100 if num_trades else 0 + avg_pnl = trades["pnl"].mean() if num_trades else 0 + equity = trades["pnl"].cumsum() + max_dd = (equity.cummax() - equity).max() if not equity.empty else 0 + return {"total_pnl": total_pnl, + "num_trades": num_trades, + "win_rate": win_rate, + "avg_pnl": avg_pnl, + "max_drawdown": max_dd} + +# === Optimization sweep === +if __name__ == "__main__": + pair = "EUR_USD" + spot = fetch_spot_history(pair, days=365) + swap_pts= fetch_swap_history(pair, days=365) + thresholds = [0.5, 1.0, 2.0, 3.0] + stop_losses = [2.0, 5.0, 10.0] + spreads = [0.1, 0.5, 1.0] + tenor_days = 30 + results = [] + for th, sl, sp in itertools.product(thresholds, stop_losses, spreads): + m = run_backtest(spot, swap_pts, th, sl, sp, tenor_days) + results.append({"threshold_bps": th, + "stop_loss_bps": sl, + "spread_bps": sp, + **m}) + df = pd.DataFrame(results) + df.to_csv("optimization_results_real.csv", index=False) + top = df.sort_values("total_pnl", ascending=False).head(10) + print("Top 10 real-forward parameter sets:") + print(top.to_string(index=False)) + diff --git a/pyvenv.cfg b/pyvenv.cfg new file mode 100644 index 0000000..4760c1f --- /dev/null +++ b/pyvenv.cfg @@ -0,0 +1,3 @@ +home = /Library/Developer/CommandLineTools/usr/bin +include-system-site-packages = false +version = 3.9.6 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3c1f4f3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +certifi==2025.7.14 +charset-normalizer==3.4.2 +idna==3.10 +oandapyV20==0.7.2 +requests==2.32.4 +urllib3==2.5.0 diff --git a/risk.py b/risk.py new file mode 100644 index 0000000..339def6 --- /dev/null +++ b/risk.py @@ -0,0 +1,32 @@ +def pnl_path(spot_series, obs_forward, notional=1_000_000): + """ + Given a time series of spot prices (list of floats) and a + locked-in forward price (obs_forward), returns a list of + PnL values under a +1 lot trade. + PnL_t = notional * (spot_t - obs_forward) + """ + return [notional * (s - obs_forward) for s in spot_series] + +import random +# e.g. 10 days of spot returns ±0.5% +base = 1.16987 +path = [] +for _ in range(10): + shock = random.uniform(-0.005, 0.005) + base = base * (1 + shock) + path.append(round(base, 6)) + +from risk import pnl_path + +# assume obs_forward from your engine, e.g. 1.17105 +obs_forward = 1.17105 +pnls = pnl_path(path, obs_forward) +print("Day-by-day PnL:", pnls) + +import numpy as np + +# compute daily PnL changes +diffs = np.diff(pnls) +# find the 5th percentile loss +var95 = -np.percentile(diffs, 5) +print(f"1-day 95% VaR: ${var95:,.2f}") diff --git a/simulate_forward.py b/simulate_forward.py new file mode 100644 index 0000000..2206034 --- /dev/null +++ b/simulate_forward.py @@ -0,0 +1,33 @@ +# simulate_forward.py + +import random +from cip import theoretical_forward, deviation_bps + +# === Simulation parameters === +spot_mid = 1.16987 # last known spot mid +r_domestic = 0.025 # e.g. USD OIS annual rate +r_foreign = 0.005 # e.g. EUR OIS annual rate +days = 30 # tenor in days for 1M +threshold_bps = 2.0 # alert threshold in bps + +# 1) Compute the “fair” 1M forward via CIP +theo_fwd = theoretical_forward(spot_mid, r_domestic, r_foreign, days) +print(f"Theoretical 1M forward: {theo_fwd:.6f}\n") + +# 2) Run 10 simulated “observed” forwards with noise ±5 bps +for i in range(1, 11): + noise_bps = random.uniform(-5, 5) + obs_fwd = theo_fwd * (1 + noise_bps / 10_000) + dev = deviation_bps(obs_fwd, theo_fwd) + + # 3) Determine if it breaches your threshold + signal = "" + if abs(dev) > threshold_bps: + direction = "Sell forward / Buy spot" if dev > 0 else "Buy forward / Sell spot" + signal = f" ⚠️ ARB SIGNAL: {dev:.2f} bps → {direction}" + + # 4) Print the result for this trial + print( + f"Sim #{i:2d}: noise={noise_bps:+.2f} bps → " + f"observed={obs_fwd:.6f} → dev={dev:+.2f} bps{signal}" + )