diff --git a/scripts/createoffers.py b/scripts/createoffers.py
index 07bc2ab..1062a7e 100755
--- a/scripts/createoffers.py
+++ b/scripts/createoffers.py
@@ -15,6 +15,9 @@ Create offers
     "min_seconds_between_bids": Add a random delay between creating bids between min and max, default 60.
     "max_seconds_between_bids": ^, default "min_seconds_between_bids" * 4
     "wallet_port_override": Used for testing.
+    "prune_state_delay": Seconds between pruning old state data, set to 0 to disable pruning.
+    "main_loop_delay": Seconds between main loop iterations.
+    "prune_state_after_seconds": Seconds to keep old state data for.
     "offers": [
         {
             "name": Offer template name, eg "Offer 0", will be automatically renamed if not unique.
@@ -58,21 +61,25 @@ Create offers
 
 """
 
-__version__ = "0.2"
+__version__ = "0.3"
 
-import os
+import argparse
 import json
-import time
+import os
 import random
 import shutil
 import signal
-import urllib
-import logging
-import argparse
+import sys
 import threading
+import time
+import traceback
+import urllib
 from urllib.request import urlopen
 
 delay_event = threading.Event()
+coins_map = {}
+read_json_api = None
+read_json_api_wallet = None
 
 DEFAULT_CONFIG_FILE: str = "createoffers.json"
 DEFAULT_STATE_FILE: str = "createoffers_state.json"
@@ -107,7 +114,7 @@ def make_json_api_func(host: str, port: int):
 
 
 def signal_handler(sig, frame) -> None:
-    logging.info("Signal {} detected.".format(sig))
+    os.write(sys.stdout.fileno(), f"Signal {sig} detected.\n".encode("utf-8"))
     delay_event.set()
 
 
@@ -235,6 +242,26 @@ def readConfig(args, known_coins):
         bid_template["coin_to"] = findCoin(bid_template["coin_to"], known_coins)
     config["num_enabled_bids"] = num_enabled
 
+    config["main_loop_delay"] = config.get("main_loop_delay", 60)
+    if config["main_loop_delay"] < 10:
+        print("Setting main_loop_delay to 10")
+        config["main_loop_delay"] = 10
+        num_changes += 1
+    if config["main_loop_delay"] > 1000:
+        print("Setting main_loop_delay to 1000")
+        config["main_loop_delay"] = 1000
+        num_changes += 1
+    config["prune_state_delay"] = config.get("prune_state_delay", 120)
+
+    seconds_in_day: int = 86400
+    config["prune_state_after_seconds"] = config.get(
+        "prune_state_after_seconds", seconds_in_day * 7
+    )
+    if config["prune_state_after_seconds"] < seconds_in_day:
+        print(f"Setting prune_state_after_seconds to {seconds_in_day}")
+        config["prune_state_after_seconds"] = seconds_in_day
+        num_changes += 1
+
     if num_changes > 0:
         shutil.copyfile(config_path, config_path + ".last")
         with open(config_path, "w") as fp:
@@ -250,7 +277,495 @@ def write_state(statefile, script_state):
         json.dump(script_state, fp, indent=4)
 
 
+def process_offers(args, config, script_state) -> None:
+    offer_templates = config["offers"]
+    if len(offer_templates) < 1:
+        return
+    if args.debug:
+        print(
+            "Processing {} offer template{}".format(
+                config["num_enabled_offers"],
+                "s" if config["num_enabled_offers"] != 1 else "",
+            )
+        )
+
+    random.shuffle(offer_templates)
+    sent_offers = read_json_api("sentoffers", {"active": "active"})
+    for offer_template in offer_templates:
+        if offer_template.get("enabled", True) is False:
+            continue
+        offers_found = 0
+
+        coin_from_data = coins_map[offer_template["coin_from"]]
+        coin_to_data = coins_map[offer_template["coin_to"]]
+
+        wallet_from = read_json_api_wallet(
+            "wallets/{}".format(coin_from_data["ticker"])
+        )
+        coin_ticker = coin_from_data["ticker"]
+        if coin_ticker == "PART" and "variant" in coin_from_data:
+            coin_variant = coin_from_data["variant"]
+            if coin_variant == "Anon":
+                coin_from_data_name = "PART_ANON"
+                wallet_balance: float = float(wallet_from["anon_balance"])
+            elif coin_variant == "Blind":
+                coin_from_data_name = "PART_BLIND"
+                wallet_balance: float = float(wallet_from["blind_balance"])
+            else:
+                raise ValueError(f"{coin_ticker} variant {coin_variant} not handled")
+        else:
+            coin_from_data_name = coin_ticker
+            wallet_balance: float = float(wallet_from["balance"])
+
+        for offer in sent_offers:
+            created_offers = script_state.get("offers", {})
+            prev_template_offers = created_offers.get(offer_template["name"], {})
+
+            if next(
+                (x for x in prev_template_offers if x["offer_id"] == offer["offer_id"]),
+                None,
+            ):
+                offers_found += 1
+                if wallet_balance <= float(offer_template["min_coin_from_amt"]):
+                    offer_id = offer["offer_id"]
+                    print(
+                        "Revoking offer {}, wallet from balance below minimum".format(
+                            offer_id
+                        )
+                    )
+                    result = read_json_api(f"revokeoffer/{offer_id}")
+                    print("revokeoffer", result)
+
+        if offers_found > 0:
+            continue
+
+        max_offer_amount: float = offer_template["amount"]
+        min_offer_amount: float = offer_template.get("amount_step", max_offer_amount)
+
+        min_wallet_from_amount: float = float(offer_template["min_coin_from_amt"])
+        if wallet_balance - min_offer_amount <= min_wallet_from_amount:
+            print(
+                "Skipping template {}, wallet from balance below minimum".format(
+                    offer_template["name"]
+                )
+            )
+            continue
+
+        offer_amount: float = max_offer_amount
+        if wallet_balance - max_offer_amount <= min_wallet_from_amount:
+            available_balance: float = wallet_balance - min_wallet_from_amount
+            min_steps: int = available_balance // min_offer_amount
+            assert min_steps > 0  # Should not be possible, checked above
+            offer_amount = min_offer_amount * min_steps
+
+        delay_next_offer_before = script_state.get("delay_next_offer_before", 0)
+        if delay_next_offer_before > int(time.time()):
+            print("Delaying offers until {}".format(delay_next_offer_before))
+            break
+
+        """
+        received_offers = read_json_api(args.port, 'offers', {'active': 'active', 'include_sent': False, 'coin_from': coin_from_data['id'], 'coin_to': coin_to_data['id']})
+        print('received_offers', received_offers)
+
+        TODO - adjust rates based on existing offers
+        """
+
+        rates = read_json_api(
+            "rates",
+            {"coin_from": coin_from_data["id"], "coin_to": coin_to_data["id"]},
+        )
+        print("Rates", rates)
+        coingecko_rate = float(rates["coingecko"]["rate_inferred"])
+        use_rate = coingecko_rate
+
+        if offer_template["ratetweakpercent"] != 0:
+            print(
+                "Adjusting rate {} by {}%.".format(
+                    use_rate, offer_template["ratetweakpercent"]
+                )
+            )
+            tweak = offer_template["ratetweakpercent"] / 100.0
+            use_rate += use_rate * tweak
+
+        if use_rate < offer_template["minrate"]:
+            print("Warning: Clamping rate to minimum.")
+            use_rate = offer_template["minrate"]
+
+        print("Creating offer for: {} at rate: {}".format(offer_template, use_rate))
+        template_from_addr = offer_template["address"]
+        offer_data = {
+            "addr_from": (-1 if template_from_addr == "auto" else template_from_addr),
+            "coin_from": coin_from_data_name,
+            "coin_to": coin_to_data["ticker"],
+            "amt_from": offer_amount,
+            "amt_var": offer_template["amount_variable"],
+            "valid_for_seconds": offer_template.get(
+                "offer_valid_seconds", config.get("offer_valid_seconds", 3600)
+            ),
+            "rate": use_rate,
+            "swap_type": offer_template.get("swap_type", "adaptor_sig"),
+            "lockhrs": "24",
+            "automation_strat_id": 1,
+        }
+        if "min_swap_amount" in offer_template:
+            offer_data["amt_bid_min"] = offer_template["min_swap_amount"]
+        if args.debug:
+            print("offer data {}".format(offer_data))
+        new_offer = read_json_api("offers/new", offer_data)
+        if "error" in new_offer:
+            raise ValueError(
+                "Server failed to create offer: {}".format(new_offer["error"])
+            )
+        print("New offer: {}".format(new_offer["offer_id"]))
+        if "offers" not in script_state:
+            script_state["offers"] = {}
+        template_name = offer_template["name"]
+        if template_name not in script_state["offers"]:
+            script_state["offers"][template_name] = []
+        script_state["offers"][template_name].append(
+            {"offer_id": new_offer["offer_id"], "time": int(time.time())}
+        )
+        max_seconds_between_offers = config["max_seconds_between_offers"]
+        min_seconds_between_offers = config["min_seconds_between_offers"]
+        time_between_offers = min_seconds_between_offers
+        if max_seconds_between_offers > min_seconds_between_offers:
+            time_between_offers = random.randint(
+                min_seconds_between_offers, max_seconds_between_offers
+            )
+
+        script_state["delay_next_offer_before"] = int(time.time()) + time_between_offers
+        write_state(args.statefile, script_state)
+
+
+def process_bids(args, config, script_state) -> None:
+    bid_templates = config["bids"]
+    if len(bid_templates) < 1:
+        return
+    random.shuffle(bid_templates)
+
+    if args.debug:
+        print(
+            "Processing {} bid template{}".format(
+                config["num_enabled_bids"],
+                "s" if config["num_enabled_bids"] != 1 else "",
+            )
+        )
+    for bid_template in bid_templates:
+        if bid_template.get("enabled", True) is False:
+            continue
+        delay_next_bid_before = script_state.get("delay_next_bid_before", 0)
+        if delay_next_bid_before > int(time.time()):
+            print("Delaying bids until {}".format(delay_next_bid_before))
+            break
+
+        # Check bids in progress
+        max_concurrent = bid_template.get("max_concurrent", 1)
+        if "bids" not in script_state:
+            script_state["bids"] = {}
+        template_name = bid_template["name"]
+        if template_name not in script_state["bids"]:
+            script_state["bids"][template_name] = []
+        previous_bids = script_state["bids"][template_name]
+
+        bids_in_progress: int = 0
+        for previous_bid in previous_bids:
+            if not previous_bid["active"]:
+                continue
+            previous_bid_id = previous_bid["bid_id"]
+            previous_bid_info = read_json_api(f"bids/{previous_bid_id}")
+            bid_state = previous_bid_info["bid_state"]
+            if bid_state in (
+                "Completed",
+                "Timed-out",
+                "Abandoned",
+                "Error",
+                "Rejected",
+            ):
+                print(f"Marking bid inactive {previous_bid_id}, state {bid_state}")
+                previous_bid["active"] = False
+                write_state(args.statefile, script_state)
+                continue
+            if bid_state in ("Sent", "Received") and previous_bid_info[
+                "expired_at"
+            ] < int(time.time()):
+                print(f"Marking bid inactive {previous_bid_id}, expired")
+                previous_bid["active"] = False
+                write_state(args.statefile, script_state)
+                continue
+            bids_in_progress += 1
+
+        if bids_in_progress >= max_concurrent:
+            print("Max concurrent bids reached for template")
+            continue
+
+        # Bidder sends coin_to and receives coin_from
+        coin_from_data = coins_map[bid_template["coin_from"]]
+        coin_to_data = coins_map[bid_template["coin_to"]]
+
+        page_limit: int = 25
+        offers_options = {
+            "active": "active",
+            "include_sent": False,
+            "coin_from": coin_from_data["id"],
+            "coin_to": coin_to_data["id"],
+            "with_extra_info": True,
+            "sort_by": "rate",
+            "sort_dir": "asc",
+            "offset": 0,
+            "limit": page_limit,
+        }
+
+        received_offers = []
+        for i in range(1000000):  # for i in itertools.count()
+            page_offers = read_json_api("offers", offers_options)
+            if len(page_offers) < 1:
+                break
+            received_offers += page_offers
+            offers_options["offset"] = offers_options["offset"] + page_limit
+            if i > 100:
+                print(f"Warning: Broke offers loop at: {i}")
+                break
+
+        if args.debug:
+            print("Received Offers", received_offers)
+
+        for offer in received_offers:
+            offer_id = offer["offer_id"]
+            offer_amount = float(offer["amount_from"])
+            offer_rate = float(offer["rate"])
+            bid_amount = bid_template["amount"]
+
+            min_swap_amount = bid_template.get(
+                "min_swap_amount", 0.01
+            )  # TODO: Make default vary per coin
+            can_adjust_offer_amount: bool = offer["amount_negotiable"]
+            can_adjust_bid_amount: bool = bid_template.get("amount_variable", True)
+            can_adjust_amount: bool = can_adjust_offer_amount and can_adjust_bid_amount
+
+            if offer_amount < min_swap_amount:
+                if args.debug:
+                    print(f"Offer amount below min swap amount bid {offer_id}")
+                continue
+
+            if can_adjust_offer_amount is False and offer_amount > bid_amount:
+                if args.debug:
+                    print(f"Bid amount too low for offer {offer_id}")
+                continue
+
+            if bid_amount > offer_amount:
+                if can_adjust_bid_amount:
+                    bid_amount = offer_amount
+                else:
+                    if args.debug:
+                        print(f"Bid amount too high for offer {offer_id}")
+                    continue
+
+            if offer_rate > bid_template["maxrate"]:
+                if args.debug:
+                    print(f"Bid rate too low for offer {offer_id}")
+                continue
+
+            sent_bids = read_json_api(
+                "sentbids",
+                {
+                    "offer_id": offer["offer_id"],
+                    "with_available_or_active": True,
+                },
+            )
+            if len(sent_bids) > 0:
+                if args.debug:
+                    print(f"Already bidding on offer {offer_id}")
+                continue
+
+            offer_identity = read_json_api("identities/{}".format(offer["addr_from"]))
+            if "address" in offer_identity:
+                id_offer_from = offer_identity
+                automation_override = id_offer_from["automation_override"]
+                if automation_override == 2:
+                    if args.debug:
+                        print(
+                            f"Not bidding on offer {offer_id}, automation_override ({automation_override})."
+                        )
+                    continue
+                if automation_override == 1:
+                    if args.debug:
+                        print(
+                            "Offer address from {}, set to always accept.".format(
+                                offer["addr_from"]
+                            )
+                        )
+                else:
+                    successful_sent_bids = id_offer_from["num_sent_bids_successful"]
+                    failed_sent_bids = id_offer_from["num_sent_bids_failed"]
+                    if failed_sent_bids > 3 and failed_sent_bids > successful_sent_bids:
+                        if args.debug:
+                            print(
+                                f"Not bidding on offer {offer_id}, too many failed bids ({failed_sent_bids})."
+                            )
+                        continue
+
+            validateamount: bool = False
+            max_coin_from_balance = bid_template.get("max_coin_from_balance", -1)
+            if max_coin_from_balance > 0:
+                wallet_from = read_json_api_wallet(
+                    "wallets/{}".format(coin_from_data["ticker"])
+                )
+                total_balance_from = float(wallet_from["balance"]) + float(
+                    wallet_from["unconfirmed"]
+                )
+                if args.debug:
+                    print(f"Total coin from balance {total_balance_from}")
+                if total_balance_from + bid_amount > max_coin_from_balance:
+                    if (
+                        can_adjust_amount
+                        and max_coin_from_balance - total_balance_from > min_swap_amount
+                    ):
+                        bid_amount = max_coin_from_balance - total_balance_from
+                        validateamount = True
+                        print(f"Reduced bid amount to {bid_amount}")
+                    else:
+                        if args.debug:
+                            print(
+                                f"Bid amount would exceed maximum wallet total for offer {offer_id}"
+                            )
+                        continue
+
+            min_coin_to_balance = bid_template["min_coin_to_balance"]
+            if min_coin_to_balance > 0:
+                wallet_to = read_json_api_wallet(
+                    "wallets/{}".format(coin_to_data["ticker"])
+                )
+
+                total_balance_to = float(wallet_to["balance"]) + float(
+                    wallet_to["unconfirmed"]
+                )
+                if args.debug:
+                    print(f"Total coin to balance {total_balance_to}")
+
+                swap_amount_to = bid_amount * offer_rate
+                if total_balance_to - swap_amount_to < min_coin_to_balance:
+                    if can_adjust_amount:
+                        adjusted_swap_amount_to = total_balance_to - min_coin_to_balance
+                        adjusted_bid_amount = adjusted_swap_amount_to / offer_rate
+
+                        if adjusted_bid_amount > min_swap_amount:
+                            bid_amount = adjusted_bid_amount
+                            validateamount = True
+                            print(f"Reduced bid amount to {bid_amount}")
+                            swap_amount_to = adjusted_bid_amount * offer_rate
+
+                if total_balance_to - swap_amount_to < min_coin_to_balance:
+                    if args.debug:
+                        print(
+                            f"Bid amount would exceed minimum coin to wallet total for offer {offer_id}"
+                        )
+                    continue
+
+            if validateamount:
+                bid_amount = read_json_api(
+                    "validateamount",
+                    {
+                        "coin": coin_from_data["ticker"],
+                        "amount": bid_amount,
+                        "method": "rounddown",
+                    },
+                )
+            bid_data = {
+                "offer_id": offer["offer_id"],
+                "amount_from": bid_amount,
+            }
+
+            if "address" in bid_template:
+                addr_from = bid_template["address"]
+                if addr_from != -1 and addr_from != "auto":
+                    bid_data["addr_from"] = addr_from
+
+            if config.get("test_mode", False):
+                print("Would create bid: {}".format(bid_data))
+                bid_id = "simulated"
+            else:
+                if args.debug:
+                    print("Creating bid: {}".format(bid_data))
+                new_bid = read_json_api("bids/new", bid_data)
+                if "error" in new_bid:
+                    raise ValueError(
+                        "Server failed to create bid: {}".format(new_bid["error"])
+                    )
+                print(
+                    "New bid: {} on offer {}".format(
+                        new_bid["bid_id"], offer["offer_id"]
+                    )
+                )
+                bid_id = new_bid["bid_id"]
+
+            script_state["bids"][template_name].append(
+                {"bid_id": bid_id, "time": int(time.time()), "active": True}
+            )
+
+            max_seconds_between_bids = config["max_seconds_between_bids"]
+            min_seconds_between_bids = config["min_seconds_between_bids"]
+            if max_seconds_between_bids > min_seconds_between_bids:
+                time_between_bids = random.randint(
+                    min_seconds_between_bids, max_seconds_between_bids
+                )
+            else:
+                time_between_bids = min_seconds_between_bids
+            script_state["delay_next_bid_before"] = int(time.time()) + time_between_bids
+            write_state(args.statefile, script_state)
+            break  # Create max one bid per iteration
+
+
+def prune_script_state(now, args, config, script_state):
+    if args.debug:
+        print("Pruning script state.")
+
+    removed_offers: int = 0
+    removed_bids: int = 0
+
+    max_ttl: int = config["prune_state_after_seconds"]
+    if "offers" in script_state:
+        for template_name, template_group in script_state["offers"].items():
+            offers_to_remove = []
+            for offer in template_group:
+                if now - offer["time"] > max_ttl:
+                    offers_to_remove.append(offer["offer_id"])
+
+            for offer_id in offers_to_remove:
+                for i, offer in enumerate(template_group):
+                    if offer_id == offer["offer_id"]:
+                        del template_group[i]
+                        removed_offers += 1
+                        break
+
+    if "bids" in script_state:
+        for template_name, template_group in script_state["bids"].items():
+            bids_to_remove = []
+            for bid in template_group:
+                if now - bid["time"] > max_ttl:
+                    bids_to_remove.append(bid["bid_id"])
+
+            for bid_id in bids_to_remove:
+                for i, bid in enumerate(template_group):
+                    if bid_id == bid["bid_id"]:
+                        del template_group[i]
+                        removed_bids += 1
+                        break
+
+    if removed_offers > 0 or removed_bids > 0:
+        print(
+            "Pruned {} offer{} and {} bid{} from script state.".format(
+                removed_offers,
+                "s" if removed_offers != 1 else "",
+                removed_bids,
+                "s" if removed_bids != 1 else "",
+            )
+        )
+    script_state["time_last_pruned_state"] = now
+    write_state(args.statefile, script_state)
+
+
 def main():
+    global read_json_api, read_json_api_wallet, coins_map
     parser = argparse.ArgumentParser(description=__doc__)
     parser.add_argument(
         "-v",
@@ -312,7 +827,6 @@ def main():
         raise ValueError(f'Config file "{args.configfile}" not found.')
 
     known_coins = read_json_api("coins")
-    coins_map = {}
     for known_coin in known_coins:
         coins_map[known_coin["name"]] = known_coin
 
@@ -323,13 +837,8 @@ def main():
 
     signal.signal(signal.SIGINT, signal_handler)
     while not delay_event.is_set():
-        # Read config each iteration so they can be modified without restarting
+        # Read config each iteration so it can be modified without restarting
         config = readConfig(args, known_coins)
-        offer_templates = config["offers"]
-        random.shuffle(offer_templates)
-
-        bid_templates = config["bids"]
-        random.shuffle(bid_templates)
 
         # override wallet api calls for testing
         if "wallet_port_override" in config:
@@ -340,482 +849,28 @@ def main():
             read_json_api_wallet = read_json_api
 
         try:
-            sent_offers = read_json_api("sentoffers", {"active": "active"})
+            process_offers(args, config, script_state)
 
-            if args.debug and len(offer_templates) > 0:
-                print(
-                    "Processing {} offer template{}".format(
-                        config["num_enabled_offers"],
-                        "s" if config["num_enabled_offers"] != 1 else "",
-                    )
-                )
-            for offer_template in offer_templates:
-                if offer_template.get("enabled", True) is False:
-                    continue
-                offers_found = 0
+            process_bids(args, config, script_state)
 
-                coin_from_data = coins_map[offer_template["coin_from"]]
-                coin_to_data = coins_map[offer_template["coin_to"]]
-
-                wallet_from = read_json_api_wallet(
-                    "wallets/{}".format(coin_from_data["ticker"])
-                )
-                coin_ticker = coin_from_data["ticker"]
-                if coin_ticker == "PART" and "variant" in coin_from_data:
-                    coin_variant = coin_from_data["variant"]
-                    if coin_variant == "Anon":
-                        coin_from_data_name = "PART_ANON"
-                        wallet_balance: float = float(wallet_from["anon_balance"])
-                    elif coin_variant == "Blind":
-                        coin_from_data_name = "PART_BLIND"
-                        wallet_balance: float = float(wallet_from["blind_balance"])
-                    else:
-                        raise ValueError(
-                            f"{coin_ticker} variant {coin_variant} not handled"
-                        )
-                else:
-                    coin_from_data_name = coin_ticker
-                    wallet_balance: float = float(wallet_from["balance"])
-
-                for offer in sent_offers:
-                    created_offers = script_state.get("offers", {})
-                    prev_template_offers = created_offers.get(
-                        offer_template["name"], {}
-                    )
-
-                    if next(
-                        (
-                            x
-                            for x in prev_template_offers
-                            if x["offer_id"] == offer["offer_id"]
-                        ),
-                        None,
-                    ):
-                        offers_found += 1
-                        if wallet_balance <= float(offer_template["min_coin_from_amt"]):
-                            offer_id = offer["offer_id"]
-                            print(
-                                "Revoking offer {}, wallet from balance below minimum".format(
-                                    offer_id
-                                )
-                            )
-                            result = read_json_api(f"revokeoffer/{offer_id}")
-                            print("revokeoffer", result)
-
-                if offers_found > 0:
-                    continue
-
-                max_offer_amount: float = offer_template["amount"]
-                min_offer_amount: float = offer_template.get(
-                    "amount_step", max_offer_amount
-                )
-
-                min_wallet_from_amount: float = float(
-                    offer_template["min_coin_from_amt"]
-                )
-                if wallet_balance - min_offer_amount <= min_wallet_from_amount:
-                    print(
-                        "Skipping template {}, wallet from balance below minimum".format(
-                            offer_template["name"]
-                        )
-                    )
-                    continue
-
-                offer_amount: float = max_offer_amount
-                if wallet_balance - max_offer_amount <= min_wallet_from_amount:
-                    available_balance: float = wallet_balance - min_wallet_from_amount
-                    min_steps: int = available_balance // min_offer_amount
-                    assert min_steps > 0  # Should not be possible, checked above
-                    offer_amount = min_offer_amount * min_steps
-
-                delay_next_offer_before = script_state.get("delay_next_offer_before", 0)
-                if delay_next_offer_before > int(time.time()):
-                    print("Delaying offers until {}".format(delay_next_offer_before))
-                    break
-
-                """
-                received_offers = read_json_api(args.port, 'offers', {'active': 'active', 'include_sent': False, 'coin_from': coin_from_data['id'], 'coin_to': coin_to_data['id']})
-                print('received_offers', received_offers)
-
-                TODO - adjust rates based on existing offers
-                """
-
-                rates = read_json_api(
-                    "rates",
-                    {"coin_from": coin_from_data["id"], "coin_to": coin_to_data["id"]},
-                )
-                print("Rates", rates)
-                coingecko_rate = float(rates["coingecko"]["rate_inferred"])
-                use_rate = coingecko_rate
-
-                if offer_template["ratetweakpercent"] != 0:
-                    print(
-                        "Adjusting rate {} by {}%.".format(
-                            use_rate, offer_template["ratetweakpercent"]
-                        )
-                    )
-                    tweak = offer_template["ratetweakpercent"] / 100.0
-                    use_rate += use_rate * tweak
-
-                if use_rate < offer_template["minrate"]:
-                    print("Warning: Clamping rate to minimum.")
-                    use_rate = offer_template["minrate"]
-
-                print(
-                    "Creating offer for: {} at rate: {}".format(
-                        offer_template, use_rate
-                    )
-                )
-                template_from_addr = offer_template["address"]
-                offer_data = {
-                    "addr_from": (
-                        -1 if template_from_addr == "auto" else template_from_addr
-                    ),
-                    "coin_from": coin_from_data_name,
-                    "coin_to": coin_to_data["ticker"],
-                    "amt_from": offer_amount,
-                    "amt_var": offer_template["amount_variable"],
-                    "valid_for_seconds": offer_template.get(
-                        "offer_valid_seconds", config.get("offer_valid_seconds", 3600)
-                    ),
-                    "rate": use_rate,
-                    "swap_type": offer_template.get("swap_type", "adaptor_sig"),
-                    "lockhrs": "24",
-                    "automation_strat_id": 1,
-                }
-                if "min_swap_amount" in offer_template:
-                    offer_data["amt_bid_min"] = offer_template["min_swap_amount"]
-                if args.debug:
-                    print("offer data {}".format(offer_data))
-                new_offer = read_json_api("offers/new", offer_data)
-                if "error" in new_offer:
-                    raise ValueError(
-                        "Server failed to create offer: {}".format(new_offer["error"])
-                    )
-                print("New offer: {}".format(new_offer["offer_id"]))
-                if "offers" not in script_state:
-                    script_state["offers"] = {}
-                template_name = offer_template["name"]
-                if template_name not in script_state["offers"]:
-                    script_state["offers"][template_name] = []
-                script_state["offers"][template_name].append(
-                    {"offer_id": new_offer["offer_id"], "time": int(time.time())}
-                )
-                max_seconds_between_offers = config["max_seconds_between_offers"]
-                min_seconds_between_offers = config["min_seconds_between_offers"]
-                time_between_offers = min_seconds_between_offers
-                if max_seconds_between_offers > min_seconds_between_offers:
-                    time_between_offers = random.randint(
-                        min_seconds_between_offers, max_seconds_between_offers
-                    )
-
-                script_state["delay_next_offer_before"] = (
-                    int(time.time()) + time_between_offers
-                )
-                write_state(args.statefile, script_state)
-
-            if args.debug and len(bid_templates) > 0:
-                print(
-                    "Processing {} bid template{}".format(
-                        config["num_enabled_bids"],
-                        "s" if config["num_enabled_bids"] != 1 else "",
-                    )
-                )
-            for bid_template in bid_templates:
-                if bid_template.get("enabled", True) is False:
-                    continue
-                delay_next_bid_before = script_state.get("delay_next_bid_before", 0)
-                if delay_next_bid_before > int(time.time()):
-                    print("Delaying bids until {}".format(delay_next_bid_before))
-                    break
-
-                # Check bids in progress
-                max_concurrent = bid_template.get("max_concurrent", 1)
-                if "bids" not in script_state:
-                    script_state["bids"] = {}
-                template_name = bid_template["name"]
-                if template_name not in script_state["bids"]:
-                    script_state["bids"][template_name] = []
-                previous_bids = script_state["bids"][template_name]
-
-                bids_in_progress: int = 0
-                for previous_bid in previous_bids:
-                    if not previous_bid["active"]:
-                        continue
-                    previous_bid_id = previous_bid["bid_id"]
-                    previous_bid_info = read_json_api(f"bids/{previous_bid_id}")
-                    bid_state = previous_bid_info["bid_state"]
-                    if bid_state in (
-                        "Completed",
-                        "Timed-out",
-                        "Abandoned",
-                        "Error",
-                        "Rejected",
-                    ):
-                        print(
-                            f"Marking bid inactive {previous_bid_id}, state {bid_state}"
-                        )
-                        previous_bid["active"] = False
-                        write_state(args.statefile, script_state)
-                        continue
-                    if bid_state in ("Sent", "Received") and previous_bid_info[
-                        "expired_at"
-                    ] < int(time.time()):
-                        print(f"Marking bid inactive {previous_bid_id}, expired")
-                        previous_bid["active"] = False
-                        write_state(args.statefile, script_state)
-                        continue
-                    bids_in_progress += 1
-
-                if bids_in_progress >= max_concurrent:
-                    print("Max concurrent bids reached for template")
-                    continue
-
-                # Bidder sends coin_to and receives coin_from
-                coin_from_data = coins_map[bid_template["coin_from"]]
-                coin_to_data = coins_map[bid_template["coin_to"]]
-
-                page_limit: int = 25
-                offers_options = {
-                    "active": "active",
-                    "include_sent": False,
-                    "coin_from": coin_from_data["id"],
-                    "coin_to": coin_to_data["id"],
-                    "with_extra_info": True,
-                    "sort_by": "rate",
-                    "sort_dir": "asc",
-                    "offset": 0,
-                    "limit": page_limit,
-                }
-
-                received_offers = []
-                for i in range(1000000):  # for i in itertools.count()
-                    page_offers = read_json_api("offers", offers_options)
-                    if len(page_offers) < 1:
-                        break
-                    received_offers += page_offers
-                    offers_options["offset"] = offers_options["offset"] + page_limit
-                    if i > 100:
-                        print(f"Warning: Broke offers loop at: {i}")
-                        break
-
-                if args.debug:
-                    print("Received Offers", received_offers)
-
-                for offer in received_offers:
-                    offer_id = offer["offer_id"]
-                    offer_amount = float(offer["amount_from"])
-                    offer_rate = float(offer["rate"])
-                    bid_amount = bid_template["amount"]
-
-                    min_swap_amount = bid_template.get(
-                        "min_swap_amount", 0.01
-                    )  # TODO: Make default vary per coin
-                    can_adjust_offer_amount: bool = offer["amount_negotiable"]
-                    can_adjust_bid_amount: bool = bid_template.get(
-                        "amount_variable", True
-                    )
-                    can_adjust_amount: bool = (
-                        can_adjust_offer_amount and can_adjust_bid_amount
-                    )
-
-                    if offer_amount < min_swap_amount:
-                        if args.debug:
-                            print(f"Offer amount below min swap amount bid {offer_id}")
-                        continue
-
-                    if can_adjust_offer_amount is False and offer_amount > bid_amount:
-                        if args.debug:
-                            print(f"Bid amount too low for offer {offer_id}")
-                        continue
-
-                    if bid_amount > offer_amount:
-                        if can_adjust_bid_amount:
-                            bid_amount = offer_amount
-                        else:
-                            if args.debug:
-                                print(f"Bid amount too high for offer {offer_id}")
-                            continue
-
-                    if offer_rate > bid_template["maxrate"]:
-                        if args.debug:
-                            print(f"Bid rate too low for offer {offer_id}")
-                        continue
-
-                    sent_bids = read_json_api(
-                        "sentbids",
-                        {
-                            "offer_id": offer["offer_id"],
-                            "with_available_or_active": True,
-                        },
-                    )
-                    if len(sent_bids) > 0:
-                        if args.debug:
-                            print(f"Already bidding on offer {offer_id}")
-                        continue
-
-                    offer_identity = read_json_api(
-                        "identities/{}".format(offer["addr_from"])
-                    )
-                    if "address" in offer_identity:
-                        id_offer_from = offer_identity
-                        automation_override = id_offer_from["automation_override"]
-                        if automation_override == 2:
-                            if args.debug:
-                                print(
-                                    f"Not bidding on offer {offer_id}, automation_override ({automation_override})."
-                                )
-                            continue
-                        if automation_override == 1:
-                            if args.debug:
-                                print(
-                                    "Offer address from {}, set to always accept.".format(
-                                        offer["addr_from"]
-                                    )
-                                )
-                        else:
-                            successful_sent_bids = id_offer_from[
-                                "num_sent_bids_successful"
-                            ]
-                            failed_sent_bids = id_offer_from["num_sent_bids_failed"]
-                            if (
-                                failed_sent_bids > 3
-                                and failed_sent_bids > successful_sent_bids
-                            ):
-                                if args.debug:
-                                    print(
-                                        f"Not bidding on offer {offer_id}, too many failed bids ({failed_sent_bids})."
-                                    )
-                                continue
-
-                    validateamount: bool = False
-                    max_coin_from_balance = bid_template.get(
-                        "max_coin_from_balance", -1
-                    )
-                    if max_coin_from_balance > 0:
-                        wallet_from = read_json_api_wallet(
-                            "wallets/{}".format(coin_from_data["ticker"])
-                        )
-                        total_balance_from = float(wallet_from["balance"]) + float(
-                            wallet_from["unconfirmed"]
-                        )
-                        if args.debug:
-                            print(f"Total coin from balance {total_balance_from}")
-                        if total_balance_from + bid_amount > max_coin_from_balance:
-                            if (
-                                can_adjust_amount
-                                and max_coin_from_balance - total_balance_from
-                                > min_swap_amount
-                            ):
-                                bid_amount = max_coin_from_balance - total_balance_from
-                                validateamount = True
-                                print(f"Reduced bid amount to {bid_amount}")
-                            else:
-                                if args.debug:
-                                    print(
-                                        f"Bid amount would exceed maximum wallet total for offer {offer_id}"
-                                    )
-                                continue
-
-                    min_coin_to_balance = bid_template["min_coin_to_balance"]
-                    if min_coin_to_balance > 0:
-                        wallet_to = read_json_api_wallet(
-                            "wallets/{}".format(coin_to_data["ticker"])
-                        )
-
-                        total_balance_to = float(wallet_to["balance"]) + float(
-                            wallet_to["unconfirmed"]
-                        )
-                        if args.debug:
-                            print(f"Total coin to balance {total_balance_to}")
-
-                        swap_amount_to = bid_amount * offer_rate
-                        if total_balance_to - swap_amount_to < min_coin_to_balance:
-                            if can_adjust_amount:
-                                adjusted_swap_amount_to = (
-                                    total_balance_to - min_coin_to_balance
-                                )
-                                adjusted_bid_amount = (
-                                    adjusted_swap_amount_to / offer_rate
-                                )
-
-                                if adjusted_bid_amount > min_swap_amount:
-                                    bid_amount = adjusted_bid_amount
-                                    validateamount = True
-                                    print(f"Reduced bid amount to {bid_amount}")
-                                    swap_amount_to = adjusted_bid_amount * offer_rate
-
-                        if total_balance_to - swap_amount_to < min_coin_to_balance:
-                            if args.debug:
-                                print(
-                                    f"Bid amount would exceed minimum coin to wallet total for offer {offer_id}"
-                                )
-                            continue
-
-                    if validateamount:
-                        bid_amount = read_json_api(
-                            "validateamount",
-                            {
-                                "coin": coin_from_data["ticker"],
-                                "amount": bid_amount,
-                                "method": "rounddown",
-                            },
-                        )
-                    bid_data = {
-                        "offer_id": offer["offer_id"],
-                        "amount_from": bid_amount,
-                    }
-
-                    if "address" in bid_template:
-                        addr_from = bid_template["address"]
-                        if addr_from != -1 and addr_from != "auto":
-                            bid_data["addr_from"] = addr_from
-
-                    if config.get("test_mode", False):
-                        print("Would create bid: {}".format(bid_data))
-                        bid_id = "simulated"
-                    else:
-                        if args.debug:
-                            print("Creating bid: {}".format(bid_data))
-                        new_bid = read_json_api("bids/new", bid_data)
-                        if "error" in new_bid:
-                            raise ValueError(
-                                "Server failed to create bid: {}".format(
-                                    new_bid["error"]
-                                )
-                            )
-                        print(
-                            "New bid: {} on offer {}".format(
-                                new_bid["bid_id"], offer["offer_id"]
-                            )
-                        )
-                        bid_id = new_bid["bid_id"]
-
-                    script_state["bids"][template_name].append(
-                        {"bid_id": bid_id, "time": int(time.time()), "active": True}
-                    )
-
-                    max_seconds_between_bids = config["max_seconds_between_bids"]
-                    min_seconds_between_bids = config["min_seconds_between_bids"]
-                    if max_seconds_between_bids > min_seconds_between_bids:
-                        time_between_bids = random.randint(
-                            min_seconds_between_bids, max_seconds_between_bids
-                        )
-                    else:
-                        time_between_bids = min_seconds_between_bids
-                    script_state["delay_next_bid_before"] = (
-                        int(time.time()) + time_between_bids
-                    )
-                    write_state(args.statefile, script_state)
-                    break  # Create max one bid per iteration
+            now = int(time.time())
+            prune_state_delay = config["prune_state_delay"]
+            if prune_state_delay > 0:
+                if (
+                    now - script_state.get("time_last_pruned_state", 0)
+                    > prune_state_delay
+                ):
+                    prune_script_state(now, args, config, script_state)
 
         except Exception as e:
             print(f"Error: {e}.")
+            if args.debug:
+                traceback.print_exc()
 
         if args.oneshot:
             break
         print("Looping indefinitely, ctrl+c to exit.")
-        delay_event.wait(60)
+        delay_event.wait(config["main_loop_delay"])
 
     print("Done.")